@pgplex/pgconsole 0.1.1 → 0.2.1

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