@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 +109 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +166 -0
- package/package.json +24 -0
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.
|
package/dist/index.d.ts
ADDED
|
@@ -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
|
+
}
|