@freestyle-sh/with-postgres 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/README.md ADDED
@@ -0,0 +1,109 @@
1
+ # @freestyle-sh/with-postgres
2
+
3
+ PostgreSQL runtime extension for Freestyle VMs. Provides a fully configured PostgreSQL database server in your Freestyle VM.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @freestyle-sh/with-postgres freestyle-sandboxes
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```typescript
14
+ import { freestyle } from "freestyle-sandboxes";
15
+ import { VmPostgres } from "@freestyle-sh/with-postgres";
16
+
17
+ const { vm } = await freestyle.vms.create({
18
+ with: {
19
+ postgres: new VmPostgres({
20
+ password: "mypassword",
21
+ database: "mydb",
22
+ version: "16", // Optional, defaults to "16"
23
+ user: "postgres", // Optional, defaults to "postgres"
24
+ }),
25
+ },
26
+ });
27
+
28
+ // Create a table
29
+ await vm.postgres.exec(`
30
+ CREATE TABLE users (
31
+ id SERIAL PRIMARY KEY,
32
+ name VARCHAR(100),
33
+ email VARCHAR(100)
34
+ )
35
+ `);
36
+
37
+ // Insert data
38
+ await vm.postgres.exec(`
39
+ INSERT INTO users (name, email) VALUES ('Alice', 'alice@example.com')
40
+ `);
41
+
42
+ // Query data
43
+ const result = await vm.postgres.query<{ id: number; name: string; email: string }>(`
44
+ SELECT * FROM users
45
+ `);
46
+
47
+ console.log(result.rows); // [{ id: 1, name: 'Alice', email: 'alice@example.com' }]
48
+ ```
49
+
50
+ ## API
51
+
52
+ ### Constructor Options
53
+
54
+ - `version?: string` - PostgreSQL version to install (default: "16")
55
+ - `password?: string` - Password for the postgres user (default: "postgres")
56
+ - `database?: string` - Default database to create (default: "postgres")
57
+ - `user?: string` - PostgreSQL user (default: "postgres")
58
+
59
+ ### Methods
60
+
61
+ #### `query<T>(sql: string): Promise<QueryResult<T>>`
62
+
63
+ Execute a SQL query and return results as JSON.
64
+
65
+ **Returns:** `{ rows: T[], rowCount: number, error?: string }`
66
+
67
+ ```typescript
68
+ const result = await vm.postgres.query<{ id: number; name: string }>(`
69
+ SELECT id, name FROM users WHERE id = 1
70
+ `);
71
+ ```
72
+
73
+ #### `exec(sql: string): Promise<{ success: boolean, error?: string }>`
74
+
75
+ Execute a SQL command without returning results (for CREATE, INSERT, UPDATE, DELETE, etc.).
76
+
77
+ ```typescript
78
+ const result = await vm.postgres.exec(`
79
+ UPDATE users SET name = 'Bob' WHERE id = 1
80
+ `);
81
+ ```
82
+
83
+ #### `createDatabase(dbName: string): Promise<{ success: boolean, error?: string }>`
84
+
85
+ Create a new database.
86
+
87
+ ```typescript
88
+ await vm.postgres.createDatabase("newdb");
89
+ ```
90
+
91
+ #### `dropDatabase(dbName: string): Promise<{ success: boolean, error?: string }>`
92
+
93
+ Drop a database.
94
+
95
+ ```typescript
96
+ await vm.postgres.dropDatabase("olddb");
97
+ ```
98
+
99
+ ## How It Works
100
+
101
+ The package uses a systemd oneshot service to install and configure PostgreSQL during VM creation:
102
+
103
+ 1. Installs PostgreSQL from apt repositories
104
+ 2. Configures password authentication
105
+ 3. Creates the default database (if specified)
106
+ 4. Enables network connections
107
+ 5. Starts PostgreSQL as a system service
108
+
109
+ The installation is fully automated and completes during VM initialization.
@@ -0,0 +1,54 @@
1
+ import { VmWithInstance, VmWith, VmSpec } from 'freestyle-sandboxes';
2
+
3
+ interface PostgresOptions {
4
+ version?: string;
5
+ password?: string;
6
+ database?: string;
7
+ user?: string;
8
+ }
9
+ interface QueryResult<T = any> {
10
+ rows: T[];
11
+ rowCount: number;
12
+ error?: string;
13
+ }
14
+ declare class VmPostgresInstance extends VmWithInstance {
15
+ private password;
16
+ private database;
17
+ private user;
18
+ constructor(options: Required<PostgresOptions>);
19
+ /**
20
+ * Execute a SQL query and return the results
21
+ */
22
+ query<T = any>(sql: string): Promise<QueryResult<T>>;
23
+ /**
24
+ * Execute a SQL command without returning results (e.g., CREATE, INSERT, UPDATE, DELETE)
25
+ */
26
+ exec(sql: string): Promise<{
27
+ success: boolean;
28
+ error?: string;
29
+ }>;
30
+ /**
31
+ * Create a new database
32
+ */
33
+ createDatabase(dbName: string): Promise<{
34
+ success: boolean;
35
+ error?: string;
36
+ }>;
37
+ /**
38
+ * Drop a database
39
+ */
40
+ dropDatabase(dbName: string): Promise<{
41
+ success: boolean;
42
+ error?: string;
43
+ }>;
44
+ }
45
+ declare class VmPostgres extends VmWith<VmPostgresInstance> {
46
+ private options;
47
+ constructor(options?: PostgresOptions);
48
+ configureSnapshotSpec(spec: VmSpec): VmSpec;
49
+ createInstance(): VmPostgresInstance;
50
+ installServiceName(): string;
51
+ }
52
+
53
+ export { VmPostgres, VmPostgresInstance };
54
+ export type { PostgresOptions, QueryResult };
package/dist/index.js ADDED
@@ -0,0 +1,166 @@
1
+ import { VmWithInstance, VmWith, VmSpec } from 'freestyle-sandboxes';
2
+
3
+ class VmPostgresInstance extends VmWithInstance {
4
+ password;
5
+ database;
6
+ user;
7
+ constructor(options) {
8
+ super();
9
+ this.password = options.password;
10
+ this.database = options.database;
11
+ this.user = options.user;
12
+ }
13
+ /**
14
+ * Execute a SQL query and return the results
15
+ */
16
+ async query(sql) {
17
+ const escapedSql = sql.replace(/'/g, "'\\''");
18
+ const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d ${this.database} -t -A -F',' -c '${escapedSql}' --json`;
19
+ const result = await this.vm.exec({
20
+ command
21
+ });
22
+ if (result.statusCode !== 0) {
23
+ return {
24
+ rows: [],
25
+ rowCount: 0,
26
+ error: result.stderr || "Query execution failed"
27
+ };
28
+ }
29
+ try {
30
+ const parsed = JSON.parse(result.stdout || "[]");
31
+ return {
32
+ rows: Array.isArray(parsed) ? parsed : [],
33
+ rowCount: Array.isArray(parsed) ? parsed.length : 0
34
+ };
35
+ } catch (e) {
36
+ return {
37
+ rows: [],
38
+ rowCount: 0,
39
+ error: `Failed to parse query results: ${e}`
40
+ };
41
+ }
42
+ }
43
+ /**
44
+ * Execute a SQL command without returning results (e.g., CREATE, INSERT, UPDATE, DELETE)
45
+ */
46
+ async exec(sql) {
47
+ const escapedSql = sql.replace(/'/g, "'\\''");
48
+ const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d ${this.database} -c '${escapedSql}'`;
49
+ const result = await this.vm.exec({
50
+ command
51
+ });
52
+ if (result.statusCode !== 0) {
53
+ return {
54
+ success: false,
55
+ error: result.stderr || "Command execution failed"
56
+ };
57
+ }
58
+ return { success: true };
59
+ }
60
+ /**
61
+ * Create a new database
62
+ */
63
+ async createDatabase(dbName) {
64
+ const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d postgres -c 'CREATE DATABASE ${dbName}'`;
65
+ const result = await this.vm.exec({
66
+ command
67
+ });
68
+ if (result.statusCode !== 0) {
69
+ return {
70
+ success: false,
71
+ error: result.stderr || "Database creation failed"
72
+ };
73
+ }
74
+ return { success: true };
75
+ }
76
+ /**
77
+ * Drop a database
78
+ */
79
+ async dropDatabase(dbName) {
80
+ const command = `PGPASSWORD='${this.password}' psql -U ${this.user} -d postgres -c 'DROP DATABASE IF EXISTS ${dbName}'`;
81
+ const result = await this.vm.exec({
82
+ command
83
+ });
84
+ if (result.statusCode !== 0) {
85
+ return {
86
+ success: false,
87
+ error: result.stderr || "Database drop failed"
88
+ };
89
+ }
90
+ return { success: true };
91
+ }
92
+ }
93
+ class VmPostgres extends VmWith {
94
+ options;
95
+ constructor(options = {}) {
96
+ super();
97
+ this.options = {
98
+ version: options.version || "16",
99
+ password: options.password || "postgres",
100
+ database: options.database || "postgres",
101
+ user: options.user || "postgres"
102
+ };
103
+ }
104
+ configureSnapshotSpec(spec) {
105
+ const installScript = `#!/bin/bash
106
+ set -e
107
+
108
+ # Install PostgreSQL
109
+ sudo apt-get update
110
+ sudo apt-get install -y postgresql-${this.options.version} postgresql-client-${this.options.version}
111
+
112
+ # Start PostgreSQL
113
+ sudo systemctl start postgresql
114
+
115
+ # Set password for postgres user
116
+ sudo -u postgres psql -c "ALTER USER postgres PASSWORD '${this.options.password}';"
117
+
118
+ # Create default database if not postgres
119
+ if [ "${this.options.database}" != "postgres" ]; then
120
+ sudo -u postgres psql -c "CREATE DATABASE ${this.options.database};"
121
+ fi
122
+
123
+ # Configure PostgreSQL to accept password authentication
124
+ echo "host all all 0.0.0.0/0 md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
125
+ echo "local all all md5" | sudo tee -a /etc/postgresql/${this.options.version}/main/pg_hba.conf
126
+
127
+ # Allow connections from all addresses
128
+ sudo sed -i "s/#listen_addresses = 'localhost'/listen_addresses = '*'/" /etc/postgresql/${this.options.version}/main/postgresql.conf
129
+
130
+ # Restart PostgreSQL to apply changes
131
+ sudo systemctl restart postgresql
132
+
133
+ # Enable PostgreSQL to start on boot
134
+ sudo systemctl enable postgresql
135
+ `;
136
+ return this.composeSpecs(
137
+ spec,
138
+ new VmSpec({
139
+ additionalFiles: {
140
+ "/opt/install-postgres.sh": {
141
+ content: installScript
142
+ }
143
+ },
144
+ systemd: {
145
+ services: [
146
+ {
147
+ name: "install-postgres",
148
+ mode: "oneshot",
149
+ deleteAfterSuccess: true,
150
+ exec: ["bash /opt/install-postgres.sh"],
151
+ timeoutSec: 600
152
+ }
153
+ ]
154
+ }
155
+ })
156
+ );
157
+ }
158
+ createInstance() {
159
+ return new VmPostgresInstance(this.options);
160
+ }
161
+ installServiceName() {
162
+ return "install-postgres.service";
163
+ }
164
+ }
165
+
166
+ export { VmPostgres, VmPostgresInstance };
package/package.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "@freestyle-sh/with-postgres",
3
+ "version": "0.2.1",
4
+ "private": false,
5
+ "dependencies": {
6
+ "freestyle-sandboxes": "^0.1.8"
7
+ },
8
+ "type": "module",
9
+ "main": "./dist/index.js",
10
+ "types": "./dist/index.d.ts",
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.ts",
14
+ "import": "./dist/index.js"
15
+ }
16
+ },
17
+ "source": "./src/index.ts",
18
+ "files": [
19
+ "dist"
20
+ ],
21
+ "scripts": {
22
+ "build": "pkgroll"
23
+ }
24
+ }