[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6CS6J1TTHC1AAC4WKZHPQGD":3,"skill-same:01M6CS6J1TTHC1AAC4WKZHPQGD":65},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":36,"linkOnly":37,"markdownContent":38,"markdownBody":39,"frontMatter":40,"refName":43,"commitSha":44,"skillDirectory":45,"forksCount":46,"repositoryHtmlUrl":47,"repositoryHomepageUrl":47,"ownerHtmlUrl":48,"ownerBlogUrl":6,"ownerDescription":49,"updatedAt":50,"install":51,"resources":64},"01M6CS6J1TTHC1AAC4WKZHPQGD","azure-postgres-ts",null,"Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package.","Sam-06060\u002Fjarvis-assistant","Sam-06060","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F208600848?v=4","01M6CRKT7C7FXD21V6HXF293S0","Samson Frank Ganta","01M6CRKTBEKYFD8YX5Z1GT99EE",6,"Apache-2.0","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fazure-postgres-ts\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000010","后端与接口","SCENARIO","使用场景",[24,25,26,27,28,29,30,31,32,33,34,35],"azure","azure postgres ts","postgres","typescript","connect","database","postgresql","package","server","js","node","community","2026-11-10 10:12:30",false,"---\nname: azure-postgres-ts\ndescription: Connect to Azure Database for PostgreSQL Flexible Server from Node.js\u002FTypeScript using the pg (node-postgres) package.\nrisk: unknown\nsource: community\ndate_added: '2026-02-27'\n---\n\n# Azure PostgreSQL for TypeScript (node-postgres)\n\nConnect to Azure Database for PostgreSQL Flexible Server using the `pg` (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication.\n\n## Installation\n\n```bash\nnpm install pg @azure\u002Fidentity\nnpm install -D @types\u002Fpg\n```\n\n## Environment Variables\n\n```bash\n# Required\nAZURE_POSTGRESQL_HOST=\u003Cserver>.postgres.database.azure.com\nAZURE_POSTGRESQL_DATABASE=\u003Cdatabase>\nAZURE_POSTGRESQL_PORT=5432\n\n# For password authentication\nAZURE_POSTGRESQL_USER=\u003Cusername>\nAZURE_POSTGRESQL_PASSWORD=\u003Cpassword>\n\n# For Entra ID authentication\nAZURE_POSTGRESQL_USER=\u003Centra-user>@\u003Cserver>   # e.g., user@contoso.com\nAZURE_POSTGRESQL_CLIENTID=\u003Cmanaged-identity-client-id>  # For user-assigned identity\n```\n\n## Authentication\n\n### Option 1: Password Authentication\n\n```typescript\nimport { Client, Pool } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }  \u002F\u002F Required for Azure\n});\n\nawait client.connect();\n```\n\n### Option 2: Microsoft Entra ID (Passwordless) - Recommended\n\n```typescript\nimport { Client, Pool } from \"pg\";\nimport { DefaultAzureCredential } from \"@azure\u002Fidentity\";\n\n\u002F\u002F For system-assigned managed identity\nconst credential = new DefaultAzureCredential();\n\n\u002F\u002F For user-assigned managed identity\n\u002F\u002F const credential = new DefaultAzureCredential({\n\u002F\u002F   managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID\n\u002F\u002F });\n\n\u002F\u002F Acquire access token for Azure PostgreSQL\nconst tokenResponse = await credential.getToken(\n  \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n);\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,  \u002F\u002F Entra ID user\n  password: tokenResponse.token,             \u002F\u002F Token as password\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\nawait client.connect();\n```\n\n## Core Workflows\n\n### 1. Single Client Connection\n\n```typescript\nimport { Client } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\ntry {\n  await client.connect();\n  \n  const result = await client.query(\"SELECT NOW() as current_time\");\n  console.log(result.rows[0].current_time);\n} finally {\n  await client.end();  \u002F\u002F Always close connection\n}\n```\n\n### 2. Connection Pool (Recommended for Production)\n\n```typescript\nimport { Pool } from \"pg\";\n\nconst pool = new Pool({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  \n  \u002F\u002F Pool configuration\n  max: 20,                    \u002F\u002F Maximum connections in pool\n  idleTimeoutMillis: 30000,   \u002F\u002F Close idle connections after 30s\n  connectionTimeoutMillis: 10000  \u002F\u002F Timeout for new connections\n});\n\n\u002F\u002F Query using pool (automatically acquires and releases connection)\nconst result = await pool.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n\n\u002F\u002F Explicit checkout for multiple queries\nconst client = await pool.connect();\ntry {\n  const res1 = await client.query(\"SELECT * FROM users\");\n  const res2 = await client.query(\"SELECT * FROM orders\");\n} finally {\n  client.release();  \u002F\u002F Return connection to pool\n}\n\n\u002F\u002F Cleanup on shutdown\nawait pool.end();\n```\n\n### 3. Parameterized Queries (Prevent SQL Injection)\n\n```typescript\n\u002F\u002F ALWAYS use parameterized queries - never concatenate user input\nconst userId = 123;\nconst email = \"user@example.com\";\n\n\u002F\u002F Single parameter\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\n\u002F\u002F Multiple parameters\nconst result = await pool.query(\n  \"INSERT INTO users (email, name, created_at) VALUES ($1, $2, NOW()) RETURNING *\",\n  [email, \"John Doe\"]\n);\n\n\u002F\u002F Array parameter\nconst ids = [1, 2, 3, 4, 5];\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = ANY($1::int[])\",\n  [ids]\n);\n```\n\n### 4. Transactions\n\n```typescript\nconst client = await pool.connect();\n\ntry {\n  await client.query(\"BEGIN\");\n  \n  const userResult = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING id\",\n    [\"user@example.com\"]\n  );\n  const userId = userResult.rows[0].id;\n  \n  await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2)\",\n    [userId, 99.99]\n  );\n  \n  await client.query(\"COMMIT\");\n} catch (error) {\n  await client.query(\"ROLLBACK\");\n  throw error;\n} finally {\n  client.release();\n}\n```\n\n### 5. Transaction Helper Function\n\n```typescript\nasync function withTransaction\u003CT>(\n  pool: Pool,\n  fn: (client: PoolClient) => Promise\u003CT>\n): Promise\u003CT> {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await fn(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    await client.query(\"ROLLBACK\");\n    throw error;\n  } finally {\n    client.release();\n  }\n}\n\n\u002F\u002F Usage\nconst order = await withTransaction(pool, async (client) => {\n  const user = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING *\",\n    [\"user@example.com\"]\n  );\n  const order = await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *\",\n    [user.rows[0].id, 99.99]\n  );\n  return order.rows[0];\n});\n```\n\n### 6. Typed Queries with TypeScript\n\n```typescript\nimport { Pool, QueryResult } from \"pg\";\n\ninterface User {\n  id: number;\n  email: string;\n  name: string;\n  created_at: Date;\n}\n\n\u002F\u002F Type the query result\nconst result: QueryResult\u003CUser> = await pool.query\u003CUser>(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\nconst user: User | undefined = result.rows[0];\n\n\u002F\u002F Type-safe insert\nasync function createUser(\n  pool: Pool,\n  email: string,\n  name: string\n): Promise\u003CUser> {\n  const result = await pool.query\u003CUser>(\n    \"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *\",\n    [email, name]\n  );\n  return result.rows[0];\n}\n```\n\n## Pool with Entra ID Token Refresh\n\nFor long-running applications, tokens expire and need refresh:\n\n```typescript\nimport { Pool, PoolConfig } from \"pg\";\nimport { DefaultAzureCredential, AccessToken } from \"@azure\u002Fidentity\";\n\nclass AzurePostgresPool {\n  private pool: Pool | null = null;\n  private credential: DefaultAzureCredential;\n  private tokenExpiry: Date | null = null;\n  private config: Omit\u003CPoolConfig, \"password\">;\n\n  constructor(config: Omit\u003CPoolConfig, \"password\">) {\n    this.credential = new DefaultAzureCredential();\n    this.config = config;\n  }\n\n  private async getToken(): Promise\u003Cstring> {\n    const tokenResponse = await this.credential.getToken(\n      \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n    );\n    this.tokenExpiry = new Date(tokenResponse.expiresOnTimestamp);\n    return tokenResponse.token;\n  }\n\n  private isTokenExpired(): boolean {\n    if (!this.tokenExpiry) return true;\n    \u002F\u002F Refresh 5 minutes before expiry\n    return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000);\n  }\n\n  async getPool(): Promise\u003CPool> {\n    if (this.pool && !this.isTokenExpired()) {\n      return this.pool;\n    }\n\n    \u002F\u002F Close existing pool if token expired\n    if (this.pool) {\n      await this.pool.end();\n    }\n\n    const token = await this.getToken();\n    this.pool = new Pool({\n      ...this.config,\n      password: token\n    });\n\n    return this.pool;\n  }\n\n  async query\u003CT>(text: string, params?: any[]): Promise\u003CQueryResult\u003CT>> {\n    const pool = await this.getPool();\n    return pool.query\u003CT>(text, params);\n  }\n\n  async end(): Promise\u003Cvoid> {\n    if (this.pool) {\n      await this.pool.end();\n      this.pool = null;\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst azurePool = new AzurePostgresPool({\n  host: process.env.AZURE_POSTGRESQL_HOST!,\n  database: process.env.AZURE_POSTGRESQL_DATABASE!,\n  user: process.env.AZURE_POSTGRESQL_USER!,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  max: 20\n});\n\nconst result = await azurePool.query(\"SELECT NOW()\");\n```\n\n## Error Handling\n\n```typescript\nimport { DatabaseError } from \"pg\";\n\ntry {\n  await pool.query(\"INSERT INTO users (email) VALUES ($1)\", [email]);\n} catch (error) {\n  if (error instanceof DatabaseError) {\n    switch (error.code) {\n      case \"23505\":  \u002F\u002F unique_violation\n        console.error(\"Duplicate entry:\", error.detail);\n        break;\n      case \"23503\":  \u002F\u002F foreign_key_violation\n        console.error(\"Foreign key constraint failed:\", error.detail);\n        break;\n      case \"42P01\":  \u002F\u002F undefined_table\n        console.error(\"Table does not exist:\", error.message);\n        break;\n      case \"28P01\":  \u002F\u002F invalid_password\n        console.error(\"Authentication failed\");\n        break;\n      case \"57P03\":  \u002F\u002F cannot_connect_now (server starting)\n        console.error(\"Server unavailable, retry later\");\n        break;\n      default:\n        console.error(`PostgreSQL error ${error.code}: ${error.message}`);\n    }\n  }\n  throw error;\n}\n```\n\n## Connection String Format\n\n```typescript\n\u002F\u002F Alternative: Use connection string\nconst pool = new Pool({\n  connectionString: `postgres:\u002F\u002F${user}:${password}@${host}:${port}\u002F${database}?sslmode=require`\n});\n\n\u002F\u002F With SSL required (Azure)\nconst connectionString = \n  `postgres:\u002F\u002Fuser:password@server.postgres.database.azure.com:5432\u002Fmydb?sslmode=require`;\n```\n\n## Pool Events\n\n```typescript\nconst pool = new Pool({ \u002F* config *\u002F });\n\npool.on(\"connect\", (client) => {\n  console.log(\"New client connected to pool\");\n});\n\npool.on(\"acquire\", (client) => {\n  console.log(\"Client checked out from pool\");\n});\n\npool.on(\"release\", (err, client) => {\n  console.log(\"Client returned to pool\");\n});\n\npool.on(\"remove\", (client) => {\n  console.log(\"Client removed from pool\");\n});\n\npool.on(\"error\", (err, client) => {\n  console.error(\"Unexpected pool error:\", err);\n});\n```\n\n## Azure-Specific Configuration\n\n| Setting | Value | Description |\n|---------|-------|-------------|\n| `ssl.rejectUnauthorized` | `true` | Always use SSL for Azure |\n| Default port | `5432` | Standard PostgreSQL port |\n| PgBouncer port | `6432` | Use when PgBouncer enabled |\n| Token scope | `https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default` | Entra ID token scope |\n| Token lifetime | ~1 hour | Refresh before expiry |\n\n## Pool Sizing Guidelines\n\n| Workload | `max` | `idleTimeoutMillis` |\n|----------|-------|---------------------|\n| Light (dev\u002Ftest) | 5-10 | 30000 |\n| Medium (production) | 20-30 | 30000 |\n| Heavy (high concurrency) | 50-100 | 10000 |\n\n> **Note**: Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections.\n\n## Best Practices\n\n1. **Always use connection pools** for production applications\n2. **Use parameterized queries** - Never concatenate user input\n3. **Always close connections** - Use `try\u002Ffinally` or connection pools\n4. **Enable SSL** - Required for Azure (`ssl: { rejectUnauthorized: true }`)\n5. **Handle token refresh** - Entra ID tokens expire after ~1 hour\n6. **Set connection timeouts** - Avoid hanging on network issues\n7. **Use transactions** - For multi-statement operations\n8. **Monitor pool metrics** - Track `pool.totalCount`, `pool.idleCount`, `pool.waitingCount`\n9. **Graceful shutdown** - Call `pool.end()` on application termination\n10. **Use TypeScript generics** - Type your query results for safety\n\n## Key Types\n\n```typescript\nimport {\n  Client,\n  Pool,\n  PoolClient,\n  PoolConfig,\n  QueryResult,\n  QueryResultRow,\n  DatabaseError,\n  QueryConfig\n} from \"pg\";\n```\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| node-postgres Docs | https:\u002F\u002Fnode-postgres.com |\n| npm Package | https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fpg |\n| GitHub Repository | https:\u002F\u002Fgithub.com\u002Fbrianc\u002Fnode-postgres |\n| Azure PostgreSQL Docs | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002F |\n| Passwordless Connection | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002Fhow-to-connect-with-managed-identity |\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n","# Azure PostgreSQL for TypeScript (node-postgres)\n\nConnect to Azure Database for PostgreSQL Flexible Server using the `pg` (node-postgres) package with support for password and Microsoft Entra ID (passwordless) authentication.\n\n## Installation\n\n```bash\nnpm install pg @azure\u002Fidentity\nnpm install -D @types\u002Fpg\n```\n\n## Environment Variables\n\n```bash\n# Required\nAZURE_POSTGRESQL_HOST=\u003Cserver>.postgres.database.azure.com\nAZURE_POSTGRESQL_DATABASE=\u003Cdatabase>\nAZURE_POSTGRESQL_PORT=5432\n\n# For password authentication\nAZURE_POSTGRESQL_USER=\u003Cusername>\nAZURE_POSTGRESQL_PASSWORD=\u003Cpassword>\n\n# For Entra ID authentication\nAZURE_POSTGRESQL_USER=\u003Centra-user>@\u003Cserver>   # e.g., user@contoso.com\nAZURE_POSTGRESQL_CLIENTID=\u003Cmanaged-identity-client-id>  # For user-assigned identity\n```\n\n## Authentication\n\n### Option 1: Password Authentication\n\n```typescript\nimport { Client, Pool } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }  \u002F\u002F Required for Azure\n});\n\nawait client.connect();\n```\n\n### Option 2: Microsoft Entra ID (Passwordless) - Recommended\n\n```typescript\nimport { Client, Pool } from \"pg\";\nimport { DefaultAzureCredential } from \"@azure\u002Fidentity\";\n\n\u002F\u002F For system-assigned managed identity\nconst credential = new DefaultAzureCredential();\n\n\u002F\u002F For user-assigned managed identity\n\u002F\u002F const credential = new DefaultAzureCredential({\n\u002F\u002F   managedIdentityClientId: process.env.AZURE_POSTGRESQL_CLIENTID\n\u002F\u002F });\n\n\u002F\u002F Acquire access token for Azure PostgreSQL\nconst tokenResponse = await credential.getToken(\n  \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n);\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,  \u002F\u002F Entra ID user\n  password: tokenResponse.token,             \u002F\u002F Token as password\n  port: Number(process.env.AZURE_POSTGRESQL_PORT) || 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\nawait client.connect();\n```\n\n## Core Workflows\n\n### 1. Single Client Connection\n\n```typescript\nimport { Client } from \"pg\";\n\nconst client = new Client({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true }\n});\n\ntry {\n  await client.connect();\n  \n  const result = await client.query(\"SELECT NOW() as current_time\");\n  console.log(result.rows[0].current_time);\n} finally {\n  await client.end();  \u002F\u002F Always close connection\n}\n```\n\n### 2. Connection Pool (Recommended for Production)\n\n```typescript\nimport { Pool } from \"pg\";\n\nconst pool = new Pool({\n  host: process.env.AZURE_POSTGRESQL_HOST,\n  database: process.env.AZURE_POSTGRESQL_DATABASE,\n  user: process.env.AZURE_POSTGRESQL_USER,\n  password: process.env.AZURE_POSTGRESQL_PASSWORD,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  \n  \u002F\u002F Pool configuration\n  max: 20,                    \u002F\u002F Maximum connections in pool\n  idleTimeoutMillis: 30000,   \u002F\u002F Close idle connections after 30s\n  connectionTimeoutMillis: 10000  \u002F\u002F Timeout for new connections\n});\n\n\u002F\u002F Query using pool (automatically acquires and releases connection)\nconst result = await pool.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n\n\u002F\u002F Explicit checkout for multiple queries\nconst client = await pool.connect();\ntry {\n  const res1 = await client.query(\"SELECT * FROM users\");\n  const res2 = await client.query(\"SELECT * FROM orders\");\n} finally {\n  client.release();  \u002F\u002F Return connection to pool\n}\n\n\u002F\u002F Cleanup on shutdown\nawait pool.end();\n```\n\n### 3. Parameterized Queries (Prevent SQL Injection)\n\n```typescript\n\u002F\u002F ALWAYS use parameterized queries - never concatenate user input\nconst userId = 123;\nconst email = \"user@example.com\";\n\n\u002F\u002F Single parameter\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\n\u002F\u002F Multiple parameters\nconst result = await pool.query(\n  \"INSERT INTO users (email, name, created_at) VALUES ($1, $2, NOW()) RETURNING *\",\n  [email, \"John Doe\"]\n);\n\n\u002F\u002F Array parameter\nconst ids = [1, 2, 3, 4, 5];\nconst result = await pool.query(\n  \"SELECT * FROM users WHERE id = ANY($1::int[])\",\n  [ids]\n);\n```\n\n### 4. Transactions\n\n```typescript\nconst client = await pool.connect();\n\ntry {\n  await client.query(\"BEGIN\");\n  \n  const userResult = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING id\",\n    [\"user@example.com\"]\n  );\n  const userId = userResult.rows[0].id;\n  \n  await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2)\",\n    [userId, 99.99]\n  );\n  \n  await client.query(\"COMMIT\");\n} catch (error) {\n  await client.query(\"ROLLBACK\");\n  throw error;\n} finally {\n  client.release();\n}\n```\n\n### 5. Transaction Helper Function\n\n```typescript\nasync function withTransaction\u003CT>(\n  pool: Pool,\n  fn: (client: PoolClient) => Promise\u003CT>\n): Promise\u003CT> {\n  const client = await pool.connect();\n  try {\n    await client.query(\"BEGIN\");\n    const result = await fn(client);\n    await client.query(\"COMMIT\");\n    return result;\n  } catch (error) {\n    await client.query(\"ROLLBACK\");\n    throw error;\n  } finally {\n    client.release();\n  }\n}\n\n\u002F\u002F Usage\nconst order = await withTransaction(pool, async (client) => {\n  const user = await client.query(\n    \"INSERT INTO users (email) VALUES ($1) RETURNING *\",\n    [\"user@example.com\"]\n  );\n  const order = await client.query(\n    \"INSERT INTO orders (user_id, total) VALUES ($1, $2) RETURNING *\",\n    [user.rows[0].id, 99.99]\n  );\n  return order.rows[0];\n});\n```\n\n### 6. Typed Queries with TypeScript\n\n```typescript\nimport { Pool, QueryResult } from \"pg\";\n\ninterface User {\n  id: number;\n  email: string;\n  name: string;\n  created_at: Date;\n}\n\n\u002F\u002F Type the query result\nconst result: QueryResult\u003CUser> = await pool.query\u003CUser>(\n  \"SELECT * FROM users WHERE id = $1\",\n  [userId]\n);\n\nconst user: User | undefined = result.rows[0];\n\n\u002F\u002F Type-safe insert\nasync function createUser(\n  pool: Pool,\n  email: string,\n  name: string\n): Promise\u003CUser> {\n  const result = await pool.query\u003CUser>(\n    \"INSERT INTO users (email, name) VALUES ($1, $2) RETURNING *\",\n    [email, name]\n  );\n  return result.rows[0];\n}\n```\n\n## Pool with Entra ID Token Refresh\n\nFor long-running applications, tokens expire and need refresh:\n\n```typescript\nimport { Pool, PoolConfig } from \"pg\";\nimport { DefaultAzureCredential, AccessToken } from \"@azure\u002Fidentity\";\n\nclass AzurePostgresPool {\n  private pool: Pool | null = null;\n  private credential: DefaultAzureCredential;\n  private tokenExpiry: Date | null = null;\n  private config: Omit\u003CPoolConfig, \"password\">;\n\n  constructor(config: Omit\u003CPoolConfig, \"password\">) {\n    this.credential = new DefaultAzureCredential();\n    this.config = config;\n  }\n\n  private async getToken(): Promise\u003Cstring> {\n    const tokenResponse = await this.credential.getToken(\n      \"https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default\"\n    );\n    this.tokenExpiry = new Date(tokenResponse.expiresOnTimestamp);\n    return tokenResponse.token;\n  }\n\n  private isTokenExpired(): boolean {\n    if (!this.tokenExpiry) return true;\n    \u002F\u002F Refresh 5 minutes before expiry\n    return new Date() >= new Date(this.tokenExpiry.getTime() - 5 * 60 * 1000);\n  }\n\n  async getPool(): Promise\u003CPool> {\n    if (this.pool && !this.isTokenExpired()) {\n      return this.pool;\n    }\n\n    \u002F\u002F Close existing pool if token expired\n    if (this.pool) {\n      await this.pool.end();\n    }\n\n    const token = await this.getToken();\n    this.pool = new Pool({\n      ...this.config,\n      password: token\n    });\n\n    return this.pool;\n  }\n\n  async query\u003CT>(text: string, params?: any[]): Promise\u003CQueryResult\u003CT>> {\n    const pool = await this.getPool();\n    return pool.query\u003CT>(text, params);\n  }\n\n  async end(): Promise\u003Cvoid> {\n    if (this.pool) {\n      await this.pool.end();\n      this.pool = null;\n    }\n  }\n}\n\n\u002F\u002F Usage\nconst azurePool = new AzurePostgresPool({\n  host: process.env.AZURE_POSTGRESQL_HOST!,\n  database: process.env.AZURE_POSTGRESQL_DATABASE!,\n  user: process.env.AZURE_POSTGRESQL_USER!,\n  port: 5432,\n  ssl: { rejectUnauthorized: true },\n  max: 20\n});\n\nconst result = await azurePool.query(\"SELECT NOW()\");\n```\n\n## Error Handling\n\n```typescript\nimport { DatabaseError } from \"pg\";\n\ntry {\n  await pool.query(\"INSERT INTO users (email) VALUES ($1)\", [email]);\n} catch (error) {\n  if (error instanceof DatabaseError) {\n    switch (error.code) {\n      case \"23505\":  \u002F\u002F unique_violation\n        console.error(\"Duplicate entry:\", error.detail);\n        break;\n      case \"23503\":  \u002F\u002F foreign_key_violation\n        console.error(\"Foreign key constraint failed:\", error.detail);\n        break;\n      case \"42P01\":  \u002F\u002F undefined_table\n        console.error(\"Table does not exist:\", error.message);\n        break;\n      case \"28P01\":  \u002F\u002F invalid_password\n        console.error(\"Authentication failed\");\n        break;\n      case \"57P03\":  \u002F\u002F cannot_connect_now (server starting)\n        console.error(\"Server unavailable, retry later\");\n        break;\n      default:\n        console.error(`PostgreSQL error ${error.code}: ${error.message}`);\n    }\n  }\n  throw error;\n}\n```\n\n## Connection String Format\n\n```typescript\n\u002F\u002F Alternative: Use connection string\nconst pool = new Pool({\n  connectionString: `postgres:\u002F\u002F${user}:${password}@${host}:${port}\u002F${database}?sslmode=require`\n});\n\n\u002F\u002F With SSL required (Azure)\nconst connectionString = \n  `postgres:\u002F\u002Fuser:password@server.postgres.database.azure.com:5432\u002Fmydb?sslmode=require`;\n```\n\n## Pool Events\n\n```typescript\nconst pool = new Pool({ \u002F* config *\u002F });\n\npool.on(\"connect\", (client) => {\n  console.log(\"New client connected to pool\");\n});\n\npool.on(\"acquire\", (client) => {\n  console.log(\"Client checked out from pool\");\n});\n\npool.on(\"release\", (err, client) => {\n  console.log(\"Client returned to pool\");\n});\n\npool.on(\"remove\", (client) => {\n  console.log(\"Client removed from pool\");\n});\n\npool.on(\"error\", (err, client) => {\n  console.error(\"Unexpected pool error:\", err);\n});\n```\n\n## Azure-Specific Configuration\n\n| Setting | Value | Description |\n|---------|-------|-------------|\n| `ssl.rejectUnauthorized` | `true` | Always use SSL for Azure |\n| Default port | `5432` | Standard PostgreSQL port |\n| PgBouncer port | `6432` | Use when PgBouncer enabled |\n| Token scope | `https:\u002F\u002Fossrdbms-aad.database.windows.net\u002F.default` | Entra ID token scope |\n| Token lifetime | ~1 hour | Refresh before expiry |\n\n## Pool Sizing Guidelines\n\n| Workload | `max` | `idleTimeoutMillis` |\n|----------|-------|---------------------|\n| Light (dev\u002Ftest) | 5-10 | 30000 |\n| Medium (production) | 20-30 | 30000 |\n| Heavy (high concurrency) | 50-100 | 10000 |\n\n> **Note**: Azure PostgreSQL has connection limits based on SKU. Check your tier's max connections.\n\n## Best Practices\n\n1. **Always use connection pools** for production applications\n2. **Use parameterized queries** - Never concatenate user input\n3. **Always close connections** - Use `try\u002Ffinally` or connection pools\n4. **Enable SSL** - Required for Azure (`ssl: { rejectUnauthorized: true }`)\n5. **Handle token refresh** - Entra ID tokens expire after ~1 hour\n6. **Set connection timeouts** - Avoid hanging on network issues\n7. **Use transactions** - For multi-statement operations\n8. **Monitor pool metrics** - Track `pool.totalCount`, `pool.idleCount`, `pool.waitingCount`\n9. **Graceful shutdown** - Call `pool.end()` on application termination\n10. **Use TypeScript generics** - Type your query results for safety\n\n## Key Types\n\n```typescript\nimport {\n  Client,\n  Pool,\n  PoolClient,\n  PoolConfig,\n  QueryResult,\n  QueryResultRow,\n  DatabaseError,\n  QueryConfig\n} from \"pg\";\n```\n\n## Reference Links\n\n| Resource | URL |\n|----------|-----|\n| node-postgres Docs | https:\u002F\u002Fnode-postgres.com |\n| npm Package | https:\u002F\u002Fwww.npmjs.com\u002Fpackage\u002Fpg |\n| GitHub Repository | https:\u002F\u002Fgithub.com\u002Fbrianc\u002Fnode-postgres |\n| Azure PostgreSQL Docs | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002F |\n| Passwordless Connection | https:\u002F\u002Flearn.microsoft.com\u002Fazure\u002Fpostgresql\u002Fflexible-server\u002Fhow-to-connect-with-managed-identity |\n\n## When to Use\nThis skill is applicable to execute the workflow or actions described in the overview.\n",{"name":5,"risk":41,"source":35,"date_added":42,"description":7},"unknown","2026-02-27","main","a50a3aa6b39fb0e1b7db88bef1bfc9c93606e126","modules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fazure-postgres-ts",0,"https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant","https:\u002F\u002Fgithub.com\u002FSam-06060","Mobile Application Developer | Kotlin | Flutter, KMP, Swfit, Android,Swift UI | Building at Saar It Resources | Creator of Jarvis (AI Assistant)","2026-06-23 05:13:38",{"source":8,"skillName":5,"commands":52,"prompt":62,"zipAvailable":63},[53,56,59],{"tool":54,"command":55},"npx","npx skills add Sam-06060\u002Fjarvis-assistant --skill azure-postgres-ts",{"tool":57,"command":58},"bunx","bunx skills add Sam-06060\u002Fjarvis-assistant --skill azure-postgres-ts",{"tool":60,"command":61},"pnpm","pnpm dlx skills add Sam-06060\u002Fjarvis-assistant --skill azure-postgres-ts","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fazure-postgres-ts\u002FSKILL.md",true,[],[66,77,92,119,138,165],{"skillNo":67,"name":68,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":69,"categories":70,"keywords":74,"publishedAt":36},"01M6GDRVS6FXX3ESH0C9DW01C0","test_skill","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fscratch\u002Ftest_skill\u002FSKILL.md",[71],{"categoryNo":72,"categoryName":73,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量",[75,76],"Test Skill","test",{"skillNo":78,"name":79,"nameZh":6,"description":80,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":83,"publishedAt":36},"01M6GDRQ40HX22MA6XWC31RHHK","zustand-store-ts","Create Zustand stores following established patterns with proper TypeScript types and middleware.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzustand-store-ts\u002FSKILL.md",[],[84,85,86,87,88,89,90,35,91,27],"zustand store ts","zustand","store","with","types","middleware","patterns","create",{"skillNo":93,"name":94,"nameZh":6,"description":95,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":96,"categories":97,"keywords":101,"publishedAt":36},"01M6GDRKEA0WE670SPFWX5GKCN","zoom-automation","Automate Zoom meeting creation, management, recordings, webinars, and participant tracking via Rube MCP (Composio). Always search tools first for current schemas.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzoom-automation\u002FSKILL.md",[98],{"categoryNo":99,"categoryName":100,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[102,103,104,105,106,107,108,109,110,111,112,113,114,115,35,116,117,118],"automation","zoom automation","zoom","first","Tools","search","MCP","via","meeting","automate","critical","creation","webinars","composio","recordings","management","tracking",{"skillNo":120,"name":121,"nameZh":6,"description":122,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":123,"categories":124,"keywords":126,"publishedAt":36},"01M6GDRFS1452PC71JQ38S24HR","zoho-crm-automation","Automate Zoho CRM tasks via Rube MCP (Composio): create\u002Fupdate records, search contacts, manage leads, and convert leads. Always search tools first for current schemas.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzoho-crm-automation\u002FSKILL.md",[125],{"categoryNo":99,"categoryName":100,"taxonomyType":21,"taxonomyTypeDesc":22},[127,102,128,129,130,131,132,35,107,133,115,134,112,111,106,135,91,136,108,137],"crm automation","zoho crm","crm","zoho crm automation","zoho","leads","contacts","records","update","tasks","manage",{"skillNo":139,"name":140,"nameZh":6,"description":141,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":142,"categories":143,"keywords":147,"publishedAt":36},"01M6GDRCKQBAVTGHX5XBH26KPZ","zod-validation-expert","Expert in Zod — TypeScript-first schema validation. Covers parsing, custom errors, refinements, type inference, and integration with React Hook Form, Next.js, and tRPC.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzod-validation-expert\u002FSKILL.md",[144],{"categoryNo":145,"categoryName":146,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[148,149,150,151,152,27,153,154,155,156,157,158,87,159,160,161,162,35,163,164],"expert","zod","zod validation expert","validation","react","safe","parsing","errors","schema validation","react hook form","hook","trpc","form","type","next","schema","Next.js",{"skillNo":166,"name":167,"nameZh":6,"description":168,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":169,"categories":170,"keywords":172,"publishedAt":36},"01M6GDR658B08K4MY0RWD4A6EH","zeroize-audit","Detects missing zeroization of sensitive data in source code and identifies zeroization removed by compiler optimizations, with assembly-level analysis, and control-flow verification. Use for auditing C\u002FC++\u002FRust code handling secrets, keys, passwords, or other sensitive data.","https:\u002F\u002Fgithub.com\u002FSam-06060\u002Fjarvis-assistant\u002Fblob\u002Fmain\u002Fmodules\u002Fagent_skills\u002Fawesome-skills\u002Fplaybooks\u002Fzeroize-audit\u002FSKILL.md",[171],{"categoryNo":99,"categoryName":100,"taxonomyType":21,"taxonomyTypeDesc":22},[173,174,175,176,177,178,179,180,35,181,182,183,184,185,186,187,188,189,190,108],"zeroize audit","audit","write","passwords","get","project","c++","activate","assembly","read","Bash","secrets","analysis","data","flow","verification","code","rust"]