{"version":3,"file":"pg.mjs","names":[],"sources":["../src/dsl/columns/pg.ts","../src/adapters/pg/adapter.ts","../src/adapters/pg/dialect.ts"],"sourcesContent":["import { ColumnBuilder } from './types'\n\n// PG-only column types. Phantom T per builder so SchemaToTypes<S> can narrow\n// each column to a useful TS shape — strings for the text-y types, number[]\n// for vector embeddings, etc.\n\n/**\n * Declare a PostgreSQL ENUM type.\n *\n * @example\n * ```ts\n * export const taskStatus = pgEnum('task_status', 'todo', 'in_progress', 'done')\n *\n * export const tasks = table('tasks', {\n *   id: uuid().primaryKey().defaultRandom(),\n *   status: taskStatus().notNull().default('todo'),\n * })\n * ```\n *\n * The factory returns a column builder whose phantom type narrows to\n * the union of the declared values — `db.selectFrom('tasks').select('status')`\n * types `status: 'todo' | 'in_progress' | 'done'`.\n *\n * Schema-level state (the enum name + values) is attached to every\n * column the factory produces so introspection / drift / emit can pick\n * it up. The snapshot + emit pipeline learns about enum types in the\n * follow-up commit; for now adopters must run the\n * `CREATE TYPE <name> AS ENUM (...)` DDL manually before any column\n * referencing the type is created.\n */\nexport interface PgEnumBuilder<TName extends string, TValues extends readonly string[]> {\n  (): PgEnumColumnBuilder<TName, TValues>\n  /** The SQL identifier — used by emit + drift detection. */\n  readonly enumName: TName\n  /** Allowed literal values, in declaration order. */\n  readonly values: TValues\n}\n\nexport class PgEnumColumnBuilder<\n  TName extends string,\n  TValues extends readonly string[],\n> extends ColumnBuilder<TValues[number]> {\n  /** The enum's SQL identifier — preserved for emit + drift detection. */\n  readonly enumName: TName\n  /** Allowed values — readonly to prevent mutation across columns. */\n  readonly values: TValues\n\n  constructor(enumName: TName, values: TValues) {\n    // The SQL data type IS the enum identifier — `status task_status`,\n    // not `status text` — so PG enforces the membership constraint.\n    super(enumName)\n    this.enumName = enumName\n    this.values = values\n  }\n}\n\n// Variadic rest forces TS to infer EACH value as its literal type, so\n// `pgEnum('s', 'todo', 'done')` resolves to\n// `PgEnumBuilder<'s', ['todo', 'done']>`. Without rest, an array\n// literal stored in a variable would widen to `string[]` and the\n// column phantom would collapse to plain `string`, defeating the\n// entire point of the helper. Adopters with a pre-existing array\n// can still spread it: `pgEnum('s', ...VALUES as const)`.\nexport function pgEnum<TName extends string, const TValues extends readonly [string, ...string[]]>(\n  name: TName,\n  ...values: TValues\n): PgEnumBuilder<TName, TValues> {\n  const factory = (): PgEnumColumnBuilder<TName, TValues> =>\n    new PgEnumColumnBuilder<TName, TValues>(name, values)\n  // Attach metadata to the factory itself so introspection that walks\n  // `Object.values(schema)` can discover enum declarations even when\n  // they're declared standalone (without a column reference).\n  return Object.assign(factory, { enumName: name, values }) as PgEnumBuilder<TName, TValues>\n}\n\nexport function tsvector(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('tsvector')\n}\n\nexport function vector(dim?: number): ColumnBuilder<number[]> {\n  return new ColumnBuilder<number[]>(dim === undefined ? 'vector' : `vector(${dim})`)\n}\n\nexport function citext(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('citext')\n}\n\nexport function money(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('money')\n}\n\nexport function inet(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('inet')\n}\n\nexport function cidr(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('cidr')\n}\n\nexport function xml(): ColumnBuilder<string> {\n  return new ColumnBuilder<string>('xml')\n}\n","import {\n  introspectPg,\n  lockTableDdl,\n  migrationsTableDdl,\n  type Dialect,\n  type MigrationAdapter,\n  type MigrationRow,\n  type SchemaSnapshot,\n} from '../../index'\n\n/**\n * Minimal client returned by PgPoolLike.connect(). Matches the shape of\n * pg.PoolClient and @neondatabase/serverless's PoolClient.\n */\nexport interface PgClientLike {\n  query<R = unknown>(\n    sql: string,\n    params?: readonly unknown[],\n  ): Promise<{ rows: R[]; rowCount: number | null }>\n  release(): void\n}\n\n/**\n * Pool-shaped contract that pgAdapter consumes. Both `pg.Pool` and\n * `@neondatabase/serverless`'s Pool match this structurally — no hard\n * dependency on the `pg` package. Adopters pick whichever pg-protocol-\n * compatible client fits their runtime (node-postgres / neon-serverless /\n * pg-cloudflare / etc).\n *\n * Edge runtimes with a different surface (Neon HTTP single-shot,\n * Cloudflare D1's batch-only model) have their own adapter packages that\n * implement MigrationAdapter directly — they don't reuse this shape.\n */\nexport interface PgPoolLike {\n  query<R = unknown>(\n    sql: string,\n    params?: readonly unknown[],\n  ): Promise<{ rows: R[]; rowCount: number | null }>\n  connect(): Promise<PgClientLike>\n}\n\nexport interface PgAdapterOptions {\n  /**\n   * A pg-protocol-compatible Pool. Concretely: pg.Pool, neon-serverless Pool,\n   * any other Pool that satisfies the {@link PgPoolLike} structural shape.\n   */\n  pool: PgPoolLike\n  /** PG schema name to scope the introspector and validate at construction. Default 'public'. */\n  schema?: string\n}\n\nconst SAFE_SCHEMA_NAME = /^[a-z_][a-z0-9_]*$/i\n\n/**\n * MigrationAdapter implementation backed by node-postgres. The pool is\n * caller-owned — close() does NOT end() the pool because adopters typically\n * share a single pool across the migrationAdapter and the KickDbClient.\n *\n * Lock semantics: single-row UPDATE WHERE locked_at IS NULL on\n * kick_migrations_lock. Only the row created by ensureMigrationTables()\n * exists, so the UPDATE either flips locked_at and returns rowCount=1\n * (we won) or matches zero rows (someone else holds it).\n */\nexport function pgAdapter(opts: PgAdapterOptions): MigrationAdapter {\n  const dialect: Dialect = 'postgres'\n  const { pool } = opts\n  const schema = opts.schema ?? 'public'\n  if (!SAFE_SCHEMA_NAME.test(schema)) {\n    // Schema name lands inside introspection queries unparameterised so guard\n    // here rather than down at the SQL boundary.\n    throw new Error(`Invalid PG schema name: ${schema}`)\n  }\n\n  return {\n    dialect,\n\n    async ensureMigrationTables() {\n      await pool.query(migrationsTableDdl(dialect))\n      await pool.query(lockTableDdl(dialect))\n    },\n\n    async listApplied(): Promise<MigrationRow[]> {\n      const r = await pool.query<{\n        id: string\n        name: string\n        hash: string\n        batch: number | string\n        applied_at: string | Date\n        direction: 'up' | 'down'\n      }>(\n        `SELECT id, name, hash, batch, applied_at, direction\n         FROM kick_migrations\n         ORDER BY applied_at ASC, id ASC`,\n      )\n      return r.rows.map((row) => ({\n        id: row.id,\n        name: row.name,\n        hash: row.hash,\n        batch: Number(row.batch),\n        appliedAt:\n          row.applied_at instanceof Date ? row.applied_at.toISOString() : String(row.applied_at),\n        direction: row.direction,\n      }))\n    },\n\n    async recordApplied(row) {\n      await pool.query(\n        `INSERT INTO kick_migrations (id, name, hash, batch, direction)\n         VALUES ($1, $2, $3, $4, $5)`,\n        [row.id, row.name, row.hash, row.batch, row.direction],\n      )\n    },\n\n    async removeApplied(id: string) {\n      await pool.query(`DELETE FROM kick_migrations WHERE id = $1`, [id])\n    },\n\n    async acquireLock(owner: string): Promise<boolean> {\n      const r = await pool.query(\n        `UPDATE kick_migrations_lock\n         SET locked_at = CURRENT_TIMESTAMP, locked_by = $1\n         WHERE id = 1 AND locked_at IS NULL`,\n        [owner],\n      )\n      return r.rowCount === 1\n    },\n\n    async releaseLock() {\n      await pool.query(\n        `UPDATE kick_migrations_lock\n         SET locked_at = NULL, locked_by = NULL\n         WHERE id = 1`,\n      )\n    },\n\n    async applySqlInTx(sql: string) {\n      const client = await pool.connect()\n      try {\n        await client.query('BEGIN')\n        await client.query(sql)\n        await client.query('COMMIT')\n      } catch (err) {\n        await client.query('ROLLBACK').catch(() => {\n          /* swallow rollback errors; we're already throwing the original */\n        })\n        throw err\n      } finally {\n        client.release()\n      }\n    },\n\n    async applySqlNoTx(sql: string) {\n      await pool.query(sql)\n    },\n\n    async introspect(): Promise<SchemaSnapshot> {\n      return introspectPg(pool, { schema })\n    },\n\n    async close() {\n      // Caller owns the pool. kickDbAdapter's shutdown lifecycle calls this so\n      // future adapter-internal teardown (e.g. cancelling pending observers)\n      // has a hook, but we deliberately don't end() the shared pool.\n    },\n  }\n}\n","// pgDialect — thin factory over Kysely's PostgresDialect so adopters\n// never have to write `import { PostgresDialect } from 'kysely'`.\n// Kysely is a pinned internal dep of the framework; surfacing it\n// through our own export keeps the adopter import surface stable\n// even when we change query backends (or fork Kysely's internals).\n\nimport { PostgresDialect, type Dialect as KyselyDialect } from 'kysely'\nimport { markDialect } from '../../dialect-marker'\n\nimport type { PgPoolLike } from './adapter'\n\nexport interface PgDialectOptions {\n  /**\n   * pg-protocol-compatible pool. Both `pg.Pool` and\n   * `@neondatabase/serverless`'s Pool match structurally; adopters\n   * pick whichever runtime fits.\n   */\n  pool: PgPoolLike\n}\n\n/**\n * Construct the dialect that `createDbClient({ dialect })` consumes.\n *\n * @example\n * ```ts\n * import { createDbClient } from '@forinda/kickjs-db'\n * import { pgDialect, pgAdapter } from '@forinda/kickjs-db/pg'\n * import { Pool } from 'pg'\n *\n * const pool = new Pool({ connectionString: process.env.DATABASE_URL })\n *\n * export const db = createDbClient({\n *   schema,\n *   dialect: pgDialect({ pool }),\n * })\n *\n * export const migrationAdapter = pgAdapter({ pool })\n * ```\n */\nexport function pgDialect(opts: PgDialectOptions): KyselyDialect {\n  // PostgresDialect's `pool` parameter is typed as `PG.Pool` in newer\n  // Kysely versions; casting through `unknown` keeps adopters using\n  // alternate clients (neon, pg-cloudflare) compatible without\n  // pulling node-postgres' typings into our public surface.\n  return markDialect(new PostgresDialect({ pool: opts.pool as unknown as never }), 'postgres')\n}\n"],"mappings":";;;;;;;;;;uKAsCA,IAAa,oBAAb,cAGU,aAA+B,CAEvC,SAEA,OAEA,YAAY,SAAiB,OAAiB,CAG5C,MAAM,QAAQ,EACd,KAAK,SAAW,SAChB,KAAK,OAAS,MAChB,CACF,EASA,SAAgB,OACd,KACA,GAAG,OAC4B,CAM/B,OAAO,OAAO,WAJZ,IAAI,oBAAoC,KAAM,MAAM,EAIxB,CAAE,SAAU,KAAM,MAAO,CAAC,CAC1D,CAEA,SAAgB,UAAkC,CAChD,OAAO,IAAI,cAAsB,UAAU,CAC7C,CAEA,SAAgB,OAAO,IAAuC,CAC5D,OAAO,IAAI,cAAwB,MAAQ,IAAA,GAAY,SAAW,UAAU,IAAI,EAAE,CACpF,CAEA,SAAgB,QAAgC,CAC9C,OAAO,IAAI,cAAsB,QAAQ,CAC3C,CAEA,SAAgB,OAA+B,CAC7C,OAAO,IAAI,cAAsB,OAAO,CAC1C,CAEA,SAAgB,MAA8B,CAC5C,OAAO,IAAI,cAAsB,MAAM,CACzC,CAEA,SAAgB,MAA8B,CAC5C,OAAO,IAAI,cAAsB,MAAM,CACzC,CAEA,SAAgB,KAA6B,CAC3C,OAAO,IAAI,cAAsB,KAAK,CACxC,CClDA,MAAM,iBAAmB,sBAYzB,SAAgB,UAAU,KAA0C,CAClE,IAAM,QAAmB,WACnB,CAAE,MAAS,KACX,OAAS,KAAK,QAAU,SAC9B,GAAI,CAAC,iBAAiB,KAAK,MAAM,EAG/B,MAAU,MAAM,2BAA2B,QAAQ,EAGrD,MAAO,CACL,QAEA,MAAM,uBAAwB,CAC5B,MAAM,KAAK,MAAM,mBAAmB,OAAO,CAAC,EAC5C,MAAM,KAAK,MAAM,aAAa,OAAO,CAAC,CACxC,EAEA,MAAM,aAAuC,CAa3C,OAAO,MAZS,KAAK,MAQnB;;yCAGF,EAAA,CACS,KAAK,IAAK,MAAS,CAC1B,GAAI,IAAI,GACR,KAAM,IAAI,KACV,KAAM,IAAI,KACV,MAAO,OAAO,IAAI,KAAK,EACvB,UACE,IAAI,sBAAsB,KAAO,IAAI,WAAW,YAAY,EAAI,OAAO,IAAI,UAAU,EACvF,UAAW,IAAI,SACjB,EAAE,CACJ,EAEA,MAAM,cAAc,IAAK,CACvB,MAAM,KAAK,MACT;sCAEA,CAAC,IAAI,GAAI,IAAI,KAAM,IAAI,KAAM,IAAI,MAAO,IAAI,SAAS,CACvD,CACF,EAEA,MAAM,cAAc,GAAY,CAC9B,MAAM,KAAK,MAAM,4CAA6C,CAAC,EAAE,CAAC,CACpE,EAEA,MAAM,YAAY,MAAiC,CAOjD,OAAO,MANS,KAAK,MACnB;;6CAGA,CAAC,KAAK,CACR,EAAA,CACS,WAAa,CACxB,EAEA,MAAM,aAAc,CAClB,MAAM,KAAK,MACT;;sBAGF,CACF,EAEA,MAAM,aAAa,IAAa,CAC9B,IAAM,OAAS,MAAM,KAAK,QAAQ,EAClC,GAAI,CACF,MAAM,OAAO,MAAM,OAAO,EAC1B,MAAM,OAAO,MAAM,GAAG,EACtB,MAAM,OAAO,MAAM,QAAQ,CAC7B,OAAS,IAAK,CAIZ,MAHA,MAAM,OAAO,MAAM,UAAU,CAAC,CAAC,UAAY,CAE3C,CAAC,EACK,GACR,QAAU,CACR,OAAO,QAAQ,CACjB,CACF,EAEA,MAAM,aAAa,IAAa,CAC9B,MAAM,KAAK,MAAM,GAAG,CACtB,EAEA,MAAM,YAAsC,CAC1C,OAAO,aAAa,KAAM,CAAE,MAAO,CAAC,CACtC,EAEA,MAAM,OAAQ,CAId,CACF,CACF,CC9HA,SAAgB,UAAU,KAAuC,CAK/D,OAAO,YAAY,IAAI,gBAAgB,CAAE,KAAM,KAAK,IAAyB,CAAC,EAAG,UAAU,CAC7F"}