@pgplex/pgconsole 0.1.1 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.mjs CHANGED
@@ -41051,10 +41051,12 @@ function requiredPlan(name) {
41051
41051
  var validSslModes = ["disable", "prefer", "require", "verify-full"];
41052
41052
  var DEFAULT_CONFIG = { users: [], groups: [], labels: [], connections: [], auth: void 0, ai: void 0, banner: void 0, license: void 0, iam: [], plan: "FREE", licenseExpiry: void 0, licenseMaxUsers: 1, licenseEmail: void 0, stripeCustomerId: void 0 };
41053
41053
  var loadedConfig = { ...DEFAULT_CONFIG };
41054
+ var demoMode = false;
41054
41055
  function isValidHexColor(color) {
41055
41056
  return /^#([0-9A-Fa-f]{3}|[0-9A-Fa-f]{6})$/.test(color);
41056
41057
  }
41057
41058
  async function loadConfig(configPath) {
41059
+ demoMode = false;
41058
41060
  const path5 = configPath || "pgconsole.toml";
41059
41061
  if (!existsSync(path5)) {
41060
41062
  throw new Error(`Config file not found: ${path5}
@@ -41496,6 +41498,24 @@ function getLicenseEmail() {
41496
41498
  function getStripeCustomerId() {
41497
41499
  return loadedConfig.stripeCustomerId;
41498
41500
  }
41501
+ function loadDemoConfig(port) {
41502
+ demoMode = true;
41503
+ loadedConfig = {
41504
+ ...DEFAULT_CONFIG,
41505
+ connections: [{
41506
+ id: "demo",
41507
+ name: "Demo Database",
41508
+ host: "127.0.0.1",
41509
+ port,
41510
+ database: "postgres",
41511
+ username: "postgres",
41512
+ ssl_mode: "disable"
41513
+ }]
41514
+ };
41515
+ }
41516
+ function isDemoMode() {
41517
+ return demoMode;
41518
+ }
41499
41519
 
41500
41520
  // server/lib/auth.ts
41501
41521
  var DEFAULT_SIGNIN_EXPIRY = "7d";
@@ -54813,6 +54833,10 @@ var GetConstraintsResponse = class _GetConstraintsResponse extends Message {
54813
54833
  * @generated from field: repeated query.v1.ConstraintInfo constraints = 1;
54814
54834
  */
54815
54835
  constraints = [];
54836
+ /**
54837
+ * @generated from field: repeated query.v1.ConstraintInfo referenced_by = 2;
54838
+ */
54839
+ referencedBy = [];
54816
54840
  constructor(data) {
54817
54841
  super();
54818
54842
  proto3.util.initPartial(data, this);
@@ -54820,7 +54844,8 @@ var GetConstraintsResponse = class _GetConstraintsResponse extends Message {
54820
54844
  static runtime = proto3;
54821
54845
  static typeName = "query.v1.GetConstraintsResponse";
54822
54846
  static fields = proto3.util.newFieldList(() => [
54823
- { no: 1, name: "constraints", kind: "message", T: ConstraintInfo, repeated: true }
54847
+ { no: 1, name: "constraints", kind: "message", T: ConstraintInfo, repeated: true },
54848
+ { no: 2, name: "referenced_by", kind: "message", T: ConstraintInfo, repeated: true }
54824
54849
  ]);
54825
54850
  static fromBinary(bytes, options) {
54826
54851
  return new _GetConstraintsResponse().fromBinary(bytes, options);
@@ -57424,6 +57449,12 @@ var AssessChangeRiskResponse = class _AssessChangeRiskResponse extends Message {
57424
57449
  * @generated from field: string error = 3;
57425
57450
  */
57426
57451
  error = "";
57452
+ /**
57453
+ * Mermaid diagram of object dependencies
57454
+ *
57455
+ * @generated from field: string dependency_graph = 4;
57456
+ */
57457
+ dependencyGraph = "";
57427
57458
  constructor(data) {
57428
57459
  super();
57429
57460
  proto3.util.initPartial(data, this);
@@ -57445,6 +57476,13 @@ var AssessChangeRiskResponse = class _AssessChangeRiskResponse extends Message {
57445
57476
  kind: "scalar",
57446
57477
  T: 9
57447
57478
  /* ScalarType.STRING */
57479
+ },
57480
+ {
57481
+ no: 4,
57482
+ name: "dependency_graph",
57483
+ kind: "scalar",
57484
+ T: 9
57485
+ /* ScalarType.STRING */
57448
57486
  }
57449
57487
  ]);
57450
57488
  static fromBinary(bytes, options) {
@@ -57549,6 +57587,8 @@ function createClient(details, appUser) {
57549
57587
  ssl: details.sslMode === "disable" ? false : details.sslMode,
57550
57588
  connect_timeout: 10,
57551
57589
  max: 1,
57590
+ onnotice: () => {
57591
+ },
57552
57592
  connection: {
57553
57593
  application_name: formatAppName(appUser),
57554
57594
  ...details.lockTimeout && { lock_timeout: details.lockTimeout },
@@ -62318,22 +62358,42 @@ function extractFunctionsFromStatement(stmt) {
62318
62358
  function getFunctionPermission(name) {
62319
62359
  return functionPermissions.get(name) ?? "read";
62320
62360
  }
62361
+ var TRANSACTION_UNSAFE_KINDS = /* @__PURE__ */ new Set([
62362
+ "create_db",
62363
+ "drop_db",
62364
+ "create_tablespace",
62365
+ "drop_tablespace",
62366
+ "alter_system",
62367
+ "vacuum",
62368
+ "cluster",
62369
+ "reindex"
62370
+ ]);
62321
62371
  async function detectRequiredPermissions(sql) {
62322
62372
  try {
62323
62373
  const parsed = await parseSql(sql);
62324
62374
  if (parsed.statements.length === 0) {
62325
- return /* @__PURE__ */ new Set(["read"]);
62375
+ return { permissions: /* @__PURE__ */ new Set(["read"]), statementCount: 0, transactionSafe: true };
62326
62376
  }
62327
62377
  const permissions = /* @__PURE__ */ new Set();
62378
+ let transactionSafe = true;
62328
62379
  for (const stmt of parsed.statements) {
62329
62380
  permissions.add(getRequiredPermission(stmt.kind));
62381
+ if (TRANSACTION_UNSAFE_KINDS.has(stmt.kind) || stmt.kind === "transaction") {
62382
+ transactionSafe = false;
62383
+ }
62384
+ if (stmt.kind === "create_index" && stmt.concurrent) {
62385
+ transactionSafe = false;
62386
+ }
62387
+ if (stmt.kind === "drop" && stmt.objectType === "index" && /\bCONCURRENTLY\b/i.test(stmt.source)) {
62388
+ transactionSafe = false;
62389
+ }
62330
62390
  for (const name of extractFunctionsFromStatement(stmt)) {
62331
62391
  permissions.add(getFunctionPermission(name));
62332
62392
  }
62333
62393
  }
62334
- return permissions;
62394
+ return { permissions, statementCount: parsed.statements.length, transactionSafe };
62335
62395
  } catch {
62336
- return /* @__PURE__ */ new Set(["admin"]);
62396
+ return { permissions: /* @__PURE__ */ new Set(["admin"]), statementCount: 1, transactionSafe: false };
62337
62397
  }
62338
62398
  }
62339
62399
 
@@ -62445,8 +62505,8 @@ var queryServiceHandlers = {
62445
62505
  if (!user) {
62446
62506
  throw new ConnectError("Authentication required", Code.Unauthenticated);
62447
62507
  }
62448
- const requiredPerms = await detectRequiredPermissions(req.sql);
62449
- requirePermissions(user, req.connectionId, requiredPerms, `execute query`);
62508
+ const analysis = await detectRequiredPermissions(req.sql);
62509
+ requirePermissions(user, req.connectionId, analysis.permissions, `execute query`);
62450
62510
  const details = getConnectionDetails(req.connectionId);
62451
62511
  const queryId = req.queryId;
62452
62512
  const client = createClient(details, user.email);
@@ -62473,7 +62533,10 @@ var queryServiceHandlers = {
62473
62533
  error: "",
62474
62534
  backendPid
62475
62535
  };
62476
- const result = await client.unsafe(req.sql);
62536
+ const sql = analysis.statementCount > 1 && analysis.transactionSafe ? `BEGIN;
62537
+ ${req.sql}
62538
+ COMMIT;` : req.sql;
62539
+ const result = await client.unsafe(sql);
62477
62540
  const executionTimeMs = Date.now() - start2;
62478
62541
  let columnMeta = [];
62479
62542
  if (result.columns) {
@@ -62796,9 +62859,16 @@ var queryServiceHandlers = {
62796
62859
  const user = await getUserFromContext(context.values);
62797
62860
  requireAnyPermission(user, req.connectionId);
62798
62861
  const details = getConnectionDetails(req.connectionId);
62799
- const constraints = await withConnection(details, async (sql) => {
62862
+ const result = await withConnection(details, async (sql) => {
62800
62863
  const rows = await sql`
62864
+ WITH target AS (
62865
+ SELECT c.oid FROM pg_class c
62866
+ JOIN pg_namespace n ON n.oid = c.relnamespace
62867
+ WHERE n.nspname = ${req.schema} AND c.relname = ${req.table}
62868
+ )
62869
+ -- Forward: constraints on this table
62801
62870
  SELECT
62871
+ 'constraint' as source,
62802
62872
  con.conname as name,
62803
62873
  CASE con.contype
62804
62874
  WHEN 'p' THEN 'PRIMARY KEY'
@@ -62817,26 +62887,42 @@ var queryServiceHandlers = {
62817
62887
  FROM pg_attribute af WHERE af.attrelid = con.confrelid AND af.attnum = ANY(con.confkey))
62818
62888
  END as ref_columns
62819
62889
  FROM pg_constraint con
62820
- JOIN pg_class c ON c.oid = con.conrelid
62821
- JOIN pg_namespace n ON n.oid = c.relnamespace
62822
- LEFT JOIN pg_attribute a ON a.attrelid = c.oid AND a.attnum = ANY(con.conkey)
62823
- WHERE n.nspname = ${req.schema}
62824
- AND c.relname = ${req.table}
62890
+ JOIN target t ON con.conrelid = t.oid
62891
+ LEFT JOIN pg_attribute a ON a.attrelid = t.oid AND a.attnum = ANY(con.conkey)
62825
62892
  GROUP BY con.oid, con.conname, con.contype, con.confrelid, con.confkey
62826
- ORDER BY
62827
- CASE con.contype WHEN 'p' THEN 1 WHEN 'f' THEN 2 WHEN 'u' THEN 3 WHEN 'c' THEN 4 ELSE 5 END,
62828
- con.conname
62893
+ UNION ALL
62894
+ -- Reverse: FK constraints from other tables referencing this table
62895
+ SELECT
62896
+ 'referenced_by' as source,
62897
+ con.conname as name,
62898
+ 'FOREIGN KEY' as type,
62899
+ (SELECT array_agg(a.attname ORDER BY array_position(con.conkey, a.attnum))
62900
+ FROM pg_attribute a WHERE a.attrelid = con.conrelid AND a.attnum = ANY(con.conkey)) as columns,
62901
+ pg_get_constraintdef(con.oid) as definition,
62902
+ (SELECT ns.nspname || '.' || cs.relname FROM pg_class cs JOIN pg_namespace ns ON ns.oid = cs.relnamespace WHERE cs.oid = con.conrelid) as ref_table,
62903
+ (SELECT array_agg(af.attname ORDER BY array_position(con.confkey, af.attnum))
62904
+ FROM pg_attribute af WHERE af.attrelid = con.confrelid AND af.attnum = ANY(con.confkey)) as ref_columns
62905
+ FROM pg_constraint con
62906
+ JOIN target t ON con.confrelid = t.oid
62907
+ WHERE con.contype = 'f'
62829
62908
  `;
62830
- return rows.map((r2) => ({
62909
+ const mapRow = (r2) => ({
62831
62910
  name: r2.name,
62832
62911
  type: r2.type,
62833
62912
  columns: r2.columns || [],
62834
62913
  definition: r2.definition,
62835
62914
  refTable: r2.ref_table || "",
62836
62915
  refColumns: r2.ref_columns || []
62837
- }));
62916
+ });
62917
+ const constraints = rows.filter((r2) => r2.source === "constraint").sort((a, b) => {
62918
+ const order = { "PRIMARY KEY": 1, "FOREIGN KEY": 2, "UNIQUE": 3, "CHECK": 4, "EXCLUDE": 5 };
62919
+ const diff = (order[a.type] ?? 6) - (order[b.type] ?? 6);
62920
+ return diff !== 0 ? diff : a.name.localeCompare(b.name);
62921
+ }).map(mapRow);
62922
+ const referencedBy = rows.filter((r2) => r2.source === "referenced_by").map(mapRow);
62923
+ return { constraints, referencedBy };
62838
62924
  }, user?.email);
62839
- return { constraints };
62925
+ return result;
62840
62926
  },
62841
62927
  async getTriggers(req, context) {
62842
62928
  if (!req.connectionId) {
@@ -92198,9 +92284,9 @@ var ASSESS_RISK = {
92198
92284
 
92199
92285
  ## Focus Areas
92200
92286
 
92201
- - **Data Loss**: Permanent deletion of data, large-scale operations (100+ rows)
92202
- - **Foreign Key Impact**: Cascading deletes, orphaned records, constraint violations
92203
- - **Dependent Objects**: Impact on views, materialized views, functions, triggers
92287
+ - **Foreign Key Cascading**: Trace every foreign key chain in the schema. A DELETE or UPDATE on a parent table may CASCADE to child tables, which may in turn cascade further. Flag the full cascade path and estimate affected scope. Also flag operations that would violate FK constraints (inserting with non-existent references, dropping referenced columns/tables).
92288
+ - **Data Loss**: Permanent deletion of data, large-scale operations (100+ rows), TRUNCATE
92289
+ - **Dependent Objects**: Impact on views, materialized views, functions, triggers that reference the affected tables
92204
92290
  - **Critical Columns**: Changes to primary keys, foreign keys, unique constraints
92205
92291
  - **Transaction Scope**: Multi-table operations, data consistency concerns
92206
92292
 
@@ -92223,7 +92309,19 @@ The operations appear safe with no major integrity concerns detected.
92223
92309
  - Focus on data integrity risks, not query performance
92224
92310
  - Consider cascading effects and dependent objects
92225
92311
  - Identify potential data loss or corruption scenarios
92226
- - Keep descriptions concise but actionable`,
92312
+ - Keep descriptions concise but actionable
92313
+
92314
+ ## Dependency Graph
92315
+
92316
+ If the operations involve dependencies between database objects, include a Mermaid flowchart showing the affected tables and how changes propagate. Use this format:
92317
+
92318
+ \`\`\`mermaid
92319
+ graph TD
92320
+ orders -->|CASCADE DELETE| order_items
92321
+ products -->|RESTRICT| order_items
92322
+ \`\`\`
92323
+
92324
+ Only include the graph if there are meaningful dependencies. Omit it for simple, independent operations.`,
92227
92325
  user: ({ sqlStatements }) => `Assess the risk of executing these SQL statements:
92228
92326
 
92229
92327
  ${sqlStatements}`
@@ -92266,6 +92364,12 @@ async function getOrRefreshSchema(connectionId, schemas) {
92266
92364
  return cached.formatted;
92267
92365
  }
92268
92366
  function parseRiskAssessment(response) {
92367
+ let dependencyGraph = "";
92368
+ const mermaidMatch = response.match(/```mermaid\n([\s\S]*?)```/);
92369
+ if (mermaidMatch) {
92370
+ dependencyGraph = mermaidMatch[1].trim();
92371
+ response = response.replace(/```mermaid\n[\s\S]*?```/, "").trim();
92372
+ }
92269
92373
  const lines = response.trim().split("\n");
92270
92374
  const firstLine = lines[0].trim().toUpperCase();
92271
92375
  const overallRisk = firstLine.match(/\b(HIGH|MODERATE|LOW)\b/)?.[1] || "MODERATE";
@@ -92297,7 +92401,7 @@ function parseRiskAssessment(response) {
92297
92401
  description: response.substring(firstLine.length).trim() || "Risk assessment completed."
92298
92402
  });
92299
92403
  }
92300
- return { overallRisk: overallRisk.toLowerCase(), findings };
92404
+ return { overallRisk: overallRisk.toLowerCase(), findings, dependencyGraph };
92301
92405
  }
92302
92406
  var aiServiceHandlers = {
92303
92407
  async listAIProviders() {
@@ -92489,11 +92593,12 @@ var aiServiceHandlers = {
92489
92593
  return {
92490
92594
  overallRisk: parsed.overallRisk,
92491
92595
  findings: parsed.findings,
92492
- error: ""
92596
+ error: "",
92597
+ dependencyGraph: parsed.dependencyGraph
92493
92598
  };
92494
92599
  } catch (err) {
92495
92600
  const message2 = err instanceof Error ? err.message : "Failed to assess change risk";
92496
- return { overallRisk: "", findings: [], error: message2 };
92601
+ return { overallRisk: "", findings: [], error: message2, dependencyGraph: "" };
92497
92602
  }
92498
92603
  }
92499
92604
  };
@@ -92527,6 +92632,183 @@ var connectRouter = expressConnectMiddleware({
92527
92632
  }
92528
92633
  });
92529
92634
 
92635
+ // server/index.ts
92636
+ import { existsSync as existsSync2 } from "fs";
92637
+
92638
+ // server/lib/demo.ts
92639
+ import { PGlite } from "@electric-sql/pglite";
92640
+ import { PGLiteSocketServer } from "@electric-sql/pglite-socket";
92641
+ var db;
92642
+ var socketServer;
92643
+ var SEED_SQL = `
92644
+ CREATE TABLE departments (
92645
+ id SERIAL PRIMARY KEY,
92646
+ name VARCHAR(100) NOT NULL,
92647
+ location VARCHAR(100),
92648
+ created_at TIMESTAMP DEFAULT NOW()
92649
+ );
92650
+
92651
+ CREATE TABLE employees (
92652
+ id SERIAL PRIMARY KEY,
92653
+ first_name VARCHAR(50) NOT NULL,
92654
+ last_name VARCHAR(50) NOT NULL,
92655
+ email VARCHAR(100) UNIQUE NOT NULL,
92656
+ department_id INTEGER REFERENCES departments(id),
92657
+ title VARCHAR(100),
92658
+ salary NUMERIC(10,2),
92659
+ hire_date DATE DEFAULT CURRENT_DATE,
92660
+ created_at TIMESTAMP DEFAULT NOW()
92661
+ );
92662
+
92663
+ CREATE TABLE projects (
92664
+ id SERIAL PRIMARY KEY,
92665
+ name VARCHAR(100) NOT NULL,
92666
+ description TEXT,
92667
+ department_id INTEGER REFERENCES departments(id),
92668
+ lead_id INTEGER REFERENCES employees(id),
92669
+ status VARCHAR(20) DEFAULT 'active' CHECK (status IN ('active', 'completed', 'on_hold')),
92670
+ start_date DATE DEFAULT CURRENT_DATE,
92671
+ created_at TIMESTAMP DEFAULT NOW()
92672
+ );
92673
+
92674
+ CREATE VIEW employee_directory AS
92675
+ SELECT
92676
+ e.id,
92677
+ e.first_name || ' ' || e.last_name AS full_name,
92678
+ e.email,
92679
+ e.title,
92680
+ d.name AS department,
92681
+ d.location
92682
+ FROM employees e
92683
+ LEFT JOIN departments d ON e.department_id = d.id
92684
+ ORDER BY e.last_name, e.first_name;
92685
+
92686
+ INSERT INTO departments (name, location) VALUES
92687
+ ('Engineering', 'San Francisco'),
92688
+ ('Marketing', 'New York'),
92689
+ ('Sales', 'Chicago'),
92690
+ ('Human Resources', 'San Francisco');
92691
+
92692
+ INSERT INTO employees (first_name, last_name, email, department_id, title, salary, hire_date) VALUES
92693
+ ('Alice', 'Chen', 'alice.chen@example.com', 1, 'Staff Engineer', 185000, '2021-03-15'),
92694
+ ('Bob', 'Smith', 'bob.smith@example.com', 1, 'Senior Engineer', 165000, '2022-01-10'),
92695
+ ('Carol', 'Johnson', 'carol.johnson@example.com', 2, 'Marketing Director', 155000, '2020-06-01'),
92696
+ ('David', 'Williams', 'david.williams@example.com', 3, 'Sales Manager', 130000, '2023-02-20'),
92697
+ ('Eva', 'Martinez', 'eva.martinez@example.com', 1, 'Engineer', 140000, '2023-07-01'),
92698
+ ('Frank', 'Brown', 'frank.brown@example.com', 2, 'Content Strategist', 110000, '2023-09-15'),
92699
+ ('Grace', 'Lee', 'grace.lee@example.com', 4, 'HR Manager', 125000, '2021-11-01'),
92700
+ ('Henry', 'Taylor', 'henry.taylor@example.com', 3, 'Account Executive', 120000, '2024-01-08'),
92701
+ ('Iris', 'Garcia', 'iris.garcia@example.com', 1, 'Engineering Manager', 175000, '2020-09-12'),
92702
+ ('Jack', 'Wilson', 'jack.wilson@example.com', 2, 'Designer', 115000, '2024-03-01');
92703
+
92704
+ INSERT INTO projects (name, description, department_id, lead_id, status, start_date) VALUES
92705
+ ('Platform Redesign', 'Modernize the core platform architecture', 1, 1, 'active', '2024-01-15'),
92706
+ ('Brand Refresh', 'Update brand guidelines and marketing materials', 2, 3, 'active', '2024-02-01'),
92707
+ ('Q1 Sales Campaign', 'Enterprise outreach for Q1', 3, 4, 'completed', '2024-01-01'),
92708
+ ('Employee Portal', 'Internal HR self-service portal', 4, 7, 'active', '2024-03-01'),
92709
+ ('API v2', 'Next generation public API', 1, 9, 'on_hold', '2024-04-01');
92710
+
92711
+ -- Materialized view: department summary stats
92712
+ CREATE MATERIALIZED VIEW department_stats AS
92713
+ SELECT
92714
+ d.id AS department_id,
92715
+ d.name AS department,
92716
+ COUNT(e.id) AS employee_count,
92717
+ COALESCE(ROUND(AVG(e.salary), 2), 0) AS avg_salary,
92718
+ COALESCE(MIN(e.hire_date), CURRENT_DATE) AS earliest_hire,
92719
+ COUNT(p.id) AS project_count
92720
+ FROM departments d
92721
+ LEFT JOIN employees e ON e.department_id = d.id
92722
+ LEFT JOIN projects p ON p.department_id = d.id
92723
+ GROUP BY d.id, d.name;
92724
+
92725
+ -- Function: look up employees by department name
92726
+ CREATE FUNCTION get_employees_by_department(dept_name TEXT)
92727
+ RETURNS TABLE(id INT, full_name TEXT, title VARCHAR, salary NUMERIC) AS $$
92728
+ BEGIN
92729
+ RETURN QUERY
92730
+ SELECT e.id, e.first_name || ' ' || e.last_name, e.title, e.salary
92731
+ FROM employees e
92732
+ JOIN departments d ON e.department_id = d.id
92733
+ WHERE d.name = dept_name
92734
+ ORDER BY e.last_name;
92735
+ END;
92736
+ $$ LANGUAGE plpgsql STABLE;
92737
+
92738
+ -- Procedure: give a percentage raise to all employees in a department
92739
+ CREATE PROCEDURE give_department_raise(dept_name TEXT, pct NUMERIC)
92740
+ LANGUAGE plpgsql AS $$
92741
+ BEGIN
92742
+ UPDATE employees SET salary = ROUND(salary * (1 + pct / 100), 2)
92743
+ WHERE department_id = (SELECT id FROM departments WHERE name = dept_name);
92744
+ END;
92745
+ $$;
92746
+
92747
+ -- analytics schema
92748
+ CREATE SCHEMA analytics;
92749
+
92750
+ CREATE TABLE analytics.page_views (
92751
+ id SERIAL PRIMARY KEY,
92752
+ path VARCHAR(200) NOT NULL,
92753
+ visitor_id UUID NOT NULL,
92754
+ referrer VARCHAR(500),
92755
+ duration_ms INTEGER,
92756
+ viewed_at TIMESTAMP DEFAULT NOW()
92757
+ );
92758
+
92759
+ CREATE TABLE analytics.events (
92760
+ id SERIAL PRIMARY KEY,
92761
+ name VARCHAR(100) NOT NULL,
92762
+ visitor_id UUID NOT NULL,
92763
+ properties JSONB DEFAULT '{}',
92764
+ created_at TIMESTAMP DEFAULT NOW()
92765
+ );
92766
+
92767
+ INSERT INTO analytics.page_views (path, visitor_id, referrer, duration_ms) VALUES
92768
+ ('/', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', 'https://google.com', 4200),
92769
+ ('/pricing', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '/', 8500),
92770
+ ('/', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', 'https://github.com', 3100),
92771
+ ('/docs', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', '/', 12400),
92772
+ ('/pricing', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', 'https://twitter.com', 6700),
92773
+ ('/', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', NULL, 2300);
92774
+
92775
+ INSERT INTO analytics.events (name, visitor_id, properties) VALUES
92776
+ ('signup_click', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '{"plan": "pro"}'),
92777
+ ('page_scroll', 'b1ffcd00-ad1c-5f09-cc7e-7ccace491b22', '{"depth": 75}'),
92778
+ ('signup_click', 'c200de11-be2d-4a00-dd8f-8ddbdf502c33', '{"plan": "free"}'),
92779
+ ('feature_click', 'a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11', '{"feature": "sql-editor"}');
92780
+
92781
+ CREATE VIEW analytics.daily_summary AS
92782
+ SELECT
92783
+ viewed_at::date AS day,
92784
+ COUNT(*) AS views,
92785
+ COUNT(DISTINCT visitor_id) AS unique_visitors,
92786
+ ROUND(AVG(duration_ms)) AS avg_duration_ms
92787
+ FROM analytics.page_views
92788
+ GROUP BY viewed_at::date;
92789
+ `;
92790
+ async function startDemoDatabase() {
92791
+ db = await PGlite.create({ debug: 0 });
92792
+ await db.exec(SEED_SQL);
92793
+ return new Promise((resolve, reject) => {
92794
+ socketServer = new PGLiteSocketServer({ db, port: 0 });
92795
+ socketServer.addEventListener("listening", (event) => {
92796
+ const detail = event.detail;
92797
+ resolve(detail.port);
92798
+ });
92799
+ socketServer.addEventListener("error", (event) => {
92800
+ reject(new Error(`Failed to start demo database: ${event}`));
92801
+ });
92802
+ socketServer.start();
92803
+ });
92804
+ }
92805
+ async function stopDemoDatabase() {
92806
+ await socketServer?.stop();
92807
+ await db?.close();
92808
+ db = void 0;
92809
+ socketServer = void 0;
92810
+ }
92811
+
92530
92812
  // server/lib/test-connections.ts
92531
92813
  async function testAllConnections() {
92532
92814
  const connections = getConnections();
@@ -92610,7 +92892,8 @@ app.get("/api/setting", (_req, res) => {
92610
92892
  licenseEmail: getLicenseEmail(),
92611
92893
  maxUsers: getLicenseMaxUsers(),
92612
92894
  userCount: getUsers().length,
92613
- stripeCustomerId: getStripeCustomerId()
92895
+ stripeCustomerId: getStripeCustomerId(),
92896
+ demo: isDemoMode()
92614
92897
  });
92615
92898
  });
92616
92899
  var clientDir = path4.join(__dirname, "client");
@@ -92631,12 +92914,19 @@ async function start() {
92631
92914
  const args = parseArgs();
92632
92915
  const port = args.port || process.env.PORT || 9876;
92633
92916
  const configPath = args.config || "pgconsole.toml";
92634
- try {
92635
- await loadConfig(configPath);
92636
- console.log(`\u2713 Loaded config from: ${path4.resolve(configPath)}`);
92637
- } catch (error) {
92638
- console.error("Failed to load config:", error instanceof Error ? error.message : error);
92639
- process.exit(1);
92917
+ if (!args.config && !existsSync2(configPath)) {
92918
+ console.log("No pgconsole.toml found \u2014 starting in demo mode...");
92919
+ const demoPort = await startDemoDatabase();
92920
+ loadDemoConfig(demoPort);
92921
+ console.log(`\u2713 Demo database started on port ${demoPort}`);
92922
+ } else {
92923
+ try {
92924
+ await loadConfig(configPath);
92925
+ console.log(`\u2713 Loaded config from: ${path4.resolve(configPath)}`);
92926
+ } catch (error) {
92927
+ console.error("Failed to load config:", error instanceof Error ? error.message : error);
92928
+ process.exit(1);
92929
+ }
92640
92930
  }
92641
92931
  const licenseExpiry = getLicenseExpiry();
92642
92932
  if (licenseExpiry) {
@@ -92663,7 +92953,7 @@ async function start() {
92663
92953
  \\ \\_\\ /\\____/
92664
92954
  \\/_/ \\_/__/
92665
92955
 
92666
- Version ${"0.1.1"}
92956
+ Version ${"0.2.0"}
92667
92957
  `);
92668
92958
  console.log(`Server running on http://localhost:${port}`);
92669
92959
  const browserUrl = false ? `http://localhost:5173` : getExternalUrl() || `http://localhost:${port}`;
@@ -92677,8 +92967,12 @@ async function start() {
92677
92967
  }
92678
92968
  process.exit(1);
92679
92969
  });
92680
- const shutdown = () => {
92970
+ const shutdown = async () => {
92681
92971
  console.log("\nShutting down...");
92972
+ try {
92973
+ if (isDemoMode()) await stopDemoDatabase();
92974
+ } catch {
92975
+ }
92682
92976
  server.close(() => {
92683
92977
  process.exit(0);
92684
92978
  });