@branchly/datasource-postgres 0.1.4 → 0.1.5

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/index.d.ts CHANGED
@@ -5,8 +5,9 @@ interface SqlResult {
5
5
  }
6
6
  type SqlRunner = (sql: string, params?: readonly unknown[]) => Promise<SqlResult>;
7
7
  interface PostgresDatasourceOptions {
8
- readonly admin: string;
8
+ readonly url: string;
9
9
  readonly prefix?: string;
10
+ readonly maintenanceDatabase?: string;
10
11
  readonly query?: SqlRunner;
11
12
  }
12
13
  declare const createPostgresDatasource: (options: PostgresDatasourceOptions) => DatasourceAdapter;
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  var PG_NAME_PATTERN = /^[a-z0-9_]+$/;
3
3
  var PG_NAME_MAX_LENGTH = 63;
4
4
  var DEFAULT_PREFIX = "app";
5
+ var DEFAULT_MAINTENANCE_DATABASE = "postgres";
5
6
  var databaseName = (prefix, key) => {
6
7
  const name = `${prefix}_${key}`;
7
8
  if (!PG_NAME_PATTERN.test(name) || name.length > PG_NAME_MAX_LENGTH) {
@@ -9,9 +10,14 @@ var databaseName = (prefix, key) => {
9
10
  }
10
11
  return name;
11
12
  };
12
- var createDefaultRunner = (admin) => async (sql, params = []) => {
13
+ var withDatabase = (connection, database) => {
14
+ const url = new URL(connection);
15
+ url.pathname = `/${database}`;
16
+ return url.toString();
17
+ };
18
+ var createDefaultRunner = (adminConnection) => async (sql, params = []) => {
13
19
  const { Client } = await import("pg");
14
- const client = new Client({ connectionString: admin });
20
+ const client = new Client({ connectionString: adminConnection });
15
21
  await client.connect();
16
22
  try {
17
23
  const result = await client.query(sql, [...params]);
@@ -22,17 +28,15 @@ var createDefaultRunner = (admin) => async (sql, params = []) => {
22
28
  };
23
29
  var createPostgresDatasource = (options) => {
24
30
  const prefix = options.prefix ?? DEFAULT_PREFIX;
25
- const query = options.query ?? createDefaultRunner(options.admin);
31
+ const maintenanceDatabase = options.maintenanceDatabase ?? DEFAULT_MAINTENANCE_DATABASE;
32
+ const adminConnection = withDatabase(options.url, maintenanceDatabase);
33
+ const query = options.query ?? createDefaultRunner(adminConnection);
26
34
  const nameOf = (key) => databaseName(prefix, key);
27
35
  return {
28
36
  id: "postgres",
29
37
  apiVersion: 1,
30
38
  capabilities: { instantClone: true, snapshot: true, isolatedPerBranch: true },
31
- resolve: (key) => {
32
- const url = new URL(options.admin);
33
- url.pathname = `/${nameOf(key)}`;
34
- return url.toString();
35
- },
39
+ resolve: (key) => withDatabase(options.url, nameOf(key)),
36
40
  exists: async (key) => {
37
41
  const result = await query("SELECT 1 FROM pg_database WHERE datname = $1", [nameOf(key)]);
38
42
  return result.rows.length > 0;
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { BranchKey, DatasourceAdapter } from 'branchly';\n\nconst PG_NAME_PATTERN = /^[a-z0-9_]+$/;\nconst PG_NAME_MAX_LENGTH = 63;\nconst DEFAULT_PREFIX = 'app';\n\nexport interface SqlResult {\n readonly rows: readonly Record<string, unknown>[];\n}\n\nexport type SqlRunner = (sql: string, params?: readonly unknown[]) => Promise<SqlResult>;\n\nexport interface PostgresDatasourceOptions {\n readonly admin: string;\n readonly prefix?: string;\n readonly query?: SqlRunner;\n}\n\nconst databaseName = (prefix: string, key: BranchKey): string => {\n const name = `${prefix}_${key}`;\n if (!PG_NAME_PATTERN.test(name) || name.length > PG_NAME_MAX_LENGTH) {\n throw new Error(`branchly: branch key \"${key}\" does not map to a valid Postgres database name (\"${name}\").`);\n }\n return name;\n};\n\nconst createDefaultRunner =\n (admin: string): SqlRunner =>\n async (sql, params = []) => {\n const { Client } = await import('pg');\n const client = new Client({ connectionString: admin });\n await client.connect();\n try {\n const result = await client.query(sql, [...params]);\n return { rows: result.rows as Record<string, unknown>[] };\n } finally {\n await client.end();\n }\n };\n\nexport const createPostgresDatasource = (options: PostgresDatasourceOptions): DatasourceAdapter => {\n const prefix = options.prefix ?? DEFAULT_PREFIX;\n const query = options.query ?? createDefaultRunner(options.admin);\n const nameOf = (key: BranchKey): string => databaseName(prefix, key);\n return {\n id: 'postgres',\n apiVersion: 1,\n capabilities: { instantClone: true, snapshot: true, isolatedPerBranch: true },\n resolve: (key) => {\n const url = new URL(options.admin);\n url.pathname = `/${nameOf(key)}`;\n return url.toString();\n },\n exists: async (key) => {\n const result = await query('SELECT 1 FROM pg_database WHERE datname = $1', [nameOf(key)]);\n return result.rows.length > 0;\n },\n list: async () => {\n const result = await query('SELECT datname FROM pg_database WHERE datname LIKE $1', [`${prefix}_%`]);\n return result.rows\n .map((row) => row.datname)\n .filter((name): name is string => typeof name === 'string')\n .map((name) => name.slice(prefix.length + 1));\n },\n create: async (key) => {\n await query(`CREATE DATABASE \"${nameOf(key)}\"`);\n },\n clone: async (from, to) => {\n await query(`CREATE DATABASE \"${nameOf(to)}\" TEMPLATE \"${nameOf(from)}\"`);\n },\n destroy: async (key) => {\n await query(`DROP DATABASE IF EXISTS \"${nameOf(key)}\"`);\n },\n };\n};\n\nexport default createPostgresDatasource;\n"],"mappings":";AAEA,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB;AAcvB,IAAM,eAAe,CAAC,QAAgB,QAA2B;AAC/D,QAAM,OAAO,GAAG,MAAM,IAAI,GAAG;AAC7B,MAAI,CAAC,gBAAgB,KAAK,IAAI,KAAK,KAAK,SAAS,oBAAoB;AACnE,UAAM,IAAI,MAAM,yBAAyB,GAAG,sDAAsD,IAAI,KAAK;AAAA,EAC7G;AACA,SAAO;AACT;AAEA,IAAM,sBACJ,CAAC,UACD,OAAO,KAAK,SAAS,CAAC,MAAM;AAC1B,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAI;AACpC,QAAM,SAAS,IAAI,OAAO,EAAE,kBAAkB,MAAM,CAAC;AACrD,QAAM,OAAO,QAAQ;AACrB,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC;AAClD,WAAO,EAAE,MAAM,OAAO,KAAkC;AAAA,EAC1D,UAAE;AACA,UAAM,OAAO,IAAI;AAAA,EACnB;AACF;AAEK,IAAM,2BAA2B,CAAC,YAA0D;AACjG,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,QAAQ,QAAQ,SAAS,oBAAoB,QAAQ,KAAK;AAChE,QAAM,SAAS,CAAC,QAA2B,aAAa,QAAQ,GAAG;AACnE,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,cAAc,EAAE,cAAc,MAAM,UAAU,MAAM,mBAAmB,KAAK;AAAA,IAC5E,SAAS,CAAC,QAAQ;AAChB,YAAM,MAAM,IAAI,IAAI,QAAQ,KAAK;AACjC,UAAI,WAAW,IAAI,OAAO,GAAG,CAAC;AAC9B,aAAO,IAAI,SAAS;AAAA,IACtB;AAAA,IACA,QAAQ,OAAO,QAAQ;AACrB,YAAM,SAAS,MAAM,MAAM,gDAAgD,CAAC,OAAO,GAAG,CAAC,CAAC;AACxF,aAAO,OAAO,KAAK,SAAS;AAAA,IAC9B;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,SAAS,MAAM,MAAM,yDAAyD,CAAC,GAAG,MAAM,IAAI,CAAC;AACnG,aAAO,OAAO,KACX,IAAI,CAAC,QAAQ,IAAI,OAAO,EACxB,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,EACzD,IAAI,CAAC,SAAS,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC;AAAA,IAChD;AAAA,IACA,QAAQ,OAAO,QAAQ;AACrB,YAAM,MAAM,oBAAoB,OAAO,GAAG,CAAC,GAAG;AAAA,IAChD;AAAA,IACA,OAAO,OAAO,MAAM,OAAO;AACzB,YAAM,MAAM,oBAAoB,OAAO,EAAE,CAAC,eAAe,OAAO,IAAI,CAAC,GAAG;AAAA,IAC1E;AAAA,IACA,SAAS,OAAO,QAAQ;AACtB,YAAM,MAAM,4BAA4B,OAAO,GAAG,CAAC,GAAG;AAAA,IACxD;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { BranchKey, DatasourceAdapter } from 'branchly';\n\nconst PG_NAME_PATTERN = /^[a-z0-9_]+$/;\nconst PG_NAME_MAX_LENGTH = 63;\nconst DEFAULT_PREFIX = 'app';\nconst DEFAULT_MAINTENANCE_DATABASE = 'postgres';\n\nexport interface SqlResult {\n readonly rows: readonly Record<string, unknown>[];\n}\n\nexport type SqlRunner = (sql: string, params?: readonly unknown[]) => Promise<SqlResult>;\n\nexport interface PostgresDatasourceOptions {\n readonly url: string;\n readonly prefix?: string;\n readonly maintenanceDatabase?: string;\n readonly query?: SqlRunner;\n}\n\nconst databaseName = (prefix: string, key: BranchKey): string => {\n const name = `${prefix}_${key}`;\n if (!PG_NAME_PATTERN.test(name) || name.length > PG_NAME_MAX_LENGTH) {\n throw new Error(`branchly: branch key \"${key}\" does not map to a valid Postgres database name (\"${name}\").`);\n }\n return name;\n};\n\nconst withDatabase = (connection: string, database: string): string => {\n const url = new URL(connection);\n url.pathname = `/${database}`;\n return url.toString();\n};\n\nconst createDefaultRunner =\n (adminConnection: string): SqlRunner =>\n async (sql, params = []) => {\n const { Client } = await import('pg');\n const client = new Client({ connectionString: adminConnection });\n await client.connect();\n try {\n const result = await client.query(sql, [...params]);\n return { rows: result.rows as Record<string, unknown>[] };\n } finally {\n await client.end();\n }\n };\n\nexport const createPostgresDatasource = (options: PostgresDatasourceOptions): DatasourceAdapter => {\n const prefix = options.prefix ?? DEFAULT_PREFIX;\n const maintenanceDatabase = options.maintenanceDatabase ?? DEFAULT_MAINTENANCE_DATABASE;\n const adminConnection = withDatabase(options.url, maintenanceDatabase);\n const query = options.query ?? createDefaultRunner(adminConnection);\n const nameOf = (key: BranchKey): string => databaseName(prefix, key);\n return {\n id: 'postgres',\n apiVersion: 1,\n capabilities: { instantClone: true, snapshot: true, isolatedPerBranch: true },\n resolve: (key) => withDatabase(options.url, nameOf(key)),\n exists: async (key) => {\n const result = await query('SELECT 1 FROM pg_database WHERE datname = $1', [nameOf(key)]);\n return result.rows.length > 0;\n },\n list: async () => {\n const result = await query('SELECT datname FROM pg_database WHERE datname LIKE $1', [`${prefix}_%`]);\n return result.rows\n .map((row) => row.datname)\n .filter((name): name is string => typeof name === 'string')\n .map((name) => name.slice(prefix.length + 1));\n },\n create: async (key) => {\n await query(`CREATE DATABASE \"${nameOf(key)}\"`);\n },\n clone: async (from, to) => {\n await query(`CREATE DATABASE \"${nameOf(to)}\" TEMPLATE \"${nameOf(from)}\"`);\n },\n destroy: async (key) => {\n await query(`DROP DATABASE IF EXISTS \"${nameOf(key)}\"`);\n },\n };\n};\n\nexport default createPostgresDatasource;\n"],"mappings":";AAEA,IAAM,kBAAkB;AACxB,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB;AACvB,IAAM,+BAA+B;AAerC,IAAM,eAAe,CAAC,QAAgB,QAA2B;AAC/D,QAAM,OAAO,GAAG,MAAM,IAAI,GAAG;AAC7B,MAAI,CAAC,gBAAgB,KAAK,IAAI,KAAK,KAAK,SAAS,oBAAoB;AACnE,UAAM,IAAI,MAAM,yBAAyB,GAAG,sDAAsD,IAAI,KAAK;AAAA,EAC7G;AACA,SAAO;AACT;AAEA,IAAM,eAAe,CAAC,YAAoB,aAA6B;AACrE,QAAM,MAAM,IAAI,IAAI,UAAU;AAC9B,MAAI,WAAW,IAAI,QAAQ;AAC3B,SAAO,IAAI,SAAS;AACtB;AAEA,IAAM,sBACJ,CAAC,oBACD,OAAO,KAAK,SAAS,CAAC,MAAM;AAC1B,QAAM,EAAE,OAAO,IAAI,MAAM,OAAO,IAAI;AACpC,QAAM,SAAS,IAAI,OAAO,EAAE,kBAAkB,gBAAgB,CAAC;AAC/D,QAAM,OAAO,QAAQ;AACrB,MAAI;AACF,UAAM,SAAS,MAAM,OAAO,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC;AAClD,WAAO,EAAE,MAAM,OAAO,KAAkC;AAAA,EAC1D,UAAE;AACA,UAAM,OAAO,IAAI;AAAA,EACnB;AACF;AAEK,IAAM,2BAA2B,CAAC,YAA0D;AACjG,QAAM,SAAS,QAAQ,UAAU;AACjC,QAAM,sBAAsB,QAAQ,uBAAuB;AAC3D,QAAM,kBAAkB,aAAa,QAAQ,KAAK,mBAAmB;AACrE,QAAM,QAAQ,QAAQ,SAAS,oBAAoB,eAAe;AAClE,QAAM,SAAS,CAAC,QAA2B,aAAa,QAAQ,GAAG;AACnE,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,YAAY;AAAA,IACZ,cAAc,EAAE,cAAc,MAAM,UAAU,MAAM,mBAAmB,KAAK;AAAA,IAC5E,SAAS,CAAC,QAAQ,aAAa,QAAQ,KAAK,OAAO,GAAG,CAAC;AAAA,IACvD,QAAQ,OAAO,QAAQ;AACrB,YAAM,SAAS,MAAM,MAAM,gDAAgD,CAAC,OAAO,GAAG,CAAC,CAAC;AACxF,aAAO,OAAO,KAAK,SAAS;AAAA,IAC9B;AAAA,IACA,MAAM,YAAY;AAChB,YAAM,SAAS,MAAM,MAAM,yDAAyD,CAAC,GAAG,MAAM,IAAI,CAAC;AACnG,aAAO,OAAO,KACX,IAAI,CAAC,QAAQ,IAAI,OAAO,EACxB,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,EACzD,IAAI,CAAC,SAAS,KAAK,MAAM,OAAO,SAAS,CAAC,CAAC;AAAA,IAChD;AAAA,IACA,QAAQ,OAAO,QAAQ;AACrB,YAAM,MAAM,oBAAoB,OAAO,GAAG,CAAC,GAAG;AAAA,IAChD;AAAA,IACA,OAAO,OAAO,MAAM,OAAO;AACzB,YAAM,MAAM,oBAAoB,OAAO,EAAE,CAAC,eAAe,OAAO,IAAI,CAAC,GAAG;AAAA,IAC1E;AAAA,IACA,SAAS,OAAO,QAAQ;AACtB,YAAM,MAAM,4BAA4B,OAAO,GAAG,CAAC,GAAG;AAAA,IACxD;AAAA,EACF;AACF;AAEA,IAAO,gBAAQ;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@branchly/datasource-postgres",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Reference Postgres datasource adapter for branchly.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -20,8 +20,8 @@
20
20
  },
21
21
  "devDependencies": {
22
22
  "@types/pg": "^8.11.10",
23
- "@branchly/adapter-test-kit": "0.1.4",
24
- "branchly": "0.1.4"
23
+ "@branchly/adapter-test-kit": "0.1.5",
24
+ "branchly": "0.1.5"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"