core-services-sdk 1.3.51 → 1.3.52
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/package.json +3 -1
- package/src/index.js +1 -0
- package/src/postgresql/connect-to-pg.js +23 -0
- package/src/postgresql/index.js +3 -0
- package/src/postgresql/start-stop-postgres-docker.js +158 -0
- package/src/postgresql/validate-schema.js +39 -0
- package/tests/postgresql/validate-schema.integration.test.js +54 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "core-services-sdk",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.52",
|
|
4
4
|
"main": "src/index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"types": "types/index.d.ts",
|
|
@@ -33,9 +33,11 @@
|
|
|
33
33
|
"fastify": "^5.4.0",
|
|
34
34
|
"google-libphonenumber": "^3.2.42",
|
|
35
35
|
"http-status": "^2.1.0",
|
|
36
|
+
"knex": "^3.1.0",
|
|
36
37
|
"mongodb": "^6.18.0",
|
|
37
38
|
"nodemailer": "^7.0.5",
|
|
38
39
|
"p-retry": "^7.0.0",
|
|
40
|
+
"pg": "^8.16.3",
|
|
39
41
|
"pino": "^9.7.0",
|
|
40
42
|
"ulid": "^3.0.1",
|
|
41
43
|
"uuid": "^11.1.0",
|
package/src/index.js
CHANGED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import knex from 'knex'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Creates and returns a Knex database connection instance.
|
|
5
|
+
*
|
|
6
|
+
* This function initializes a Knex client configured for PostgreSQL
|
|
7
|
+
* using the provided connection configuration.
|
|
8
|
+
*
|
|
9
|
+
* @param {object|string} connection
|
|
10
|
+
* Knex connection configuration.
|
|
11
|
+
* Can be a connection object or a connection string.
|
|
12
|
+
*
|
|
13
|
+
* @returns {import('knex').Knex}
|
|
14
|
+
* A configured Knex database instance.
|
|
15
|
+
*/
|
|
16
|
+
export const connectToPg = (connection) => {
|
|
17
|
+
const db = knex({
|
|
18
|
+
connection,
|
|
19
|
+
client: 'pg',
|
|
20
|
+
})
|
|
21
|
+
|
|
22
|
+
return db
|
|
23
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Starts a PostgreSQL Docker container for testing purposes.
|
|
5
|
+
*
|
|
6
|
+
* If a container with the same name already exists, it will be stopped and removed first.
|
|
7
|
+
* The function blocks until PostgreSQL is ready to accept connections.
|
|
8
|
+
*
|
|
9
|
+
* @param {object} options
|
|
10
|
+
* @param {number} [options.port=5433]
|
|
11
|
+
* Host port to bind PostgreSQL to.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} [options.containerName='postgres-test']
|
|
14
|
+
* Docker container name.
|
|
15
|
+
*
|
|
16
|
+
* @param {string} [options.user='testuser']
|
|
17
|
+
* Database user name.
|
|
18
|
+
*
|
|
19
|
+
* @param {string} [options.pass='testpass']
|
|
20
|
+
* Database user password.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} [options.db='testdb']
|
|
23
|
+
* Database name.
|
|
24
|
+
*
|
|
25
|
+
* @returns {void}
|
|
26
|
+
*
|
|
27
|
+
* @throws {Error}
|
|
28
|
+
* Throws if PostgreSQL does not become ready within the expected time.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export function startPostgres({
|
|
32
|
+
port = 5433,
|
|
33
|
+
containerName = 'postgres-test',
|
|
34
|
+
user = 'testuser',
|
|
35
|
+
pass = 'testpass',
|
|
36
|
+
db = 'testdb',
|
|
37
|
+
}) {
|
|
38
|
+
console.log(`[PgTest] Starting PostgreSQL on port ${port}...`)
|
|
39
|
+
|
|
40
|
+
stopPostgres(containerName)
|
|
41
|
+
|
|
42
|
+
execSync(
|
|
43
|
+
`docker run -d \
|
|
44
|
+
--name ${containerName} \
|
|
45
|
+
-e POSTGRES_USER=${user} \
|
|
46
|
+
-e POSTGRES_PASSWORD=${pass} \
|
|
47
|
+
-e POSTGRES_DB=${db} \
|
|
48
|
+
-p ${port}:5432 \
|
|
49
|
+
postgres:16`,
|
|
50
|
+
{ stdio: 'inherit' },
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
waitForPostgres(containerName, user, db)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Stops and removes a PostgreSQL Docker container used for testing.
|
|
58
|
+
*
|
|
59
|
+
* If the container does not exist, the function exits silently.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} [containerName='postgres-test']
|
|
62
|
+
* Docker container name.
|
|
63
|
+
*
|
|
64
|
+
* @returns {void}
|
|
65
|
+
*/
|
|
66
|
+
|
|
67
|
+
export function stopPostgres(containerName = 'postgres-test') {
|
|
68
|
+
console.log(`[PgTest] Stopping PostgreSQL...`)
|
|
69
|
+
try {
|
|
70
|
+
execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' })
|
|
71
|
+
} catch {}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Checks whether PostgreSQL inside the Docker container
|
|
76
|
+
* is ready to accept connections.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} containerName
|
|
79
|
+
* Docker container name.
|
|
80
|
+
*
|
|
81
|
+
* @param {string} user
|
|
82
|
+
* Database user name.
|
|
83
|
+
*
|
|
84
|
+
* @param {string} db
|
|
85
|
+
* Database name.
|
|
86
|
+
*
|
|
87
|
+
* @returns {boolean}
|
|
88
|
+
* Returns true if PostgreSQL is ready, otherwise false.
|
|
89
|
+
*/
|
|
90
|
+
|
|
91
|
+
export function isPostgresReady(containerName, user, db) {
|
|
92
|
+
try {
|
|
93
|
+
execSync(
|
|
94
|
+
`docker exec ${containerName} psql -U ${user} -d ${db} -c "SELECT 1"`,
|
|
95
|
+
{ stdio: 'ignore' },
|
|
96
|
+
)
|
|
97
|
+
return true
|
|
98
|
+
} catch {
|
|
99
|
+
return false
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Waits until PostgreSQL inside the Docker container
|
|
105
|
+
* is ready to accept connections.
|
|
106
|
+
*
|
|
107
|
+
* Retries for a fixed number of attempts before failing.
|
|
108
|
+
*
|
|
109
|
+
* @param {string} containerName
|
|
110
|
+
* Docker container name.
|
|
111
|
+
*
|
|
112
|
+
* @param {string} user
|
|
113
|
+
* Database user name.
|
|
114
|
+
*
|
|
115
|
+
* @param {string} db
|
|
116
|
+
* Database name.
|
|
117
|
+
*
|
|
118
|
+
* @returns {void}
|
|
119
|
+
*
|
|
120
|
+
* @throws {Error}
|
|
121
|
+
* Throws if PostgreSQL does not become ready within the timeout.
|
|
122
|
+
*/
|
|
123
|
+
|
|
124
|
+
export function waitForPostgres(containerName, user, db) {
|
|
125
|
+
console.log(`[PgTest] Waiting for PostgreSQL to be ready...`)
|
|
126
|
+
|
|
127
|
+
const maxRetries = 20
|
|
128
|
+
let retries = 0
|
|
129
|
+
let ready = false
|
|
130
|
+
|
|
131
|
+
while (!ready && retries < maxRetries) {
|
|
132
|
+
ready = isPostgresReady(containerName, user, db)
|
|
133
|
+
if (!ready) {
|
|
134
|
+
retries++
|
|
135
|
+
execSync(`sleep 1`)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (!ready) {
|
|
140
|
+
throw new Error(
|
|
141
|
+
`[PgTest] PostgreSQL failed to start within ${maxRetries} seconds`,
|
|
142
|
+
)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
console.log(`[PgTest] PostgreSQL is ready.`)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Build a PostgreSQL connection URI
|
|
150
|
+
* @param {object} opts
|
|
151
|
+
* @param {string} opts.user
|
|
152
|
+
* @param {string} opts.pass
|
|
153
|
+
* @param {number} opts.port
|
|
154
|
+
* @param {string} opts.db
|
|
155
|
+
*/
|
|
156
|
+
export function buildPostgresUri({ user, pass, port, db }) {
|
|
157
|
+
return `postgres://${user}:${pass}@localhost:${port}/${db}`
|
|
158
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { connectToPg } from './connect-to-pg.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Validates that the specified database tables exist.
|
|
5
|
+
*
|
|
6
|
+
* Checks all provided table names and collects any missing tables.
|
|
7
|
+
* Throws a single error listing all missing tables after validation completes.
|
|
8
|
+
*
|
|
9
|
+
* @param {string|object} connection
|
|
10
|
+
* Database connection configuration.
|
|
11
|
+
* Can be a database connection URI or a Knex connection object.
|
|
12
|
+
*
|
|
13
|
+
* @param {string[]} tables
|
|
14
|
+
* List of required table names to validate.
|
|
15
|
+
*
|
|
16
|
+
* @returns {Promise<void>}
|
|
17
|
+
*
|
|
18
|
+
* @throws {Error}
|
|
19
|
+
* Throws an error if one or more required tables are missing.
|
|
20
|
+
*/
|
|
21
|
+
export async function validateSchema(connection, tables) {
|
|
22
|
+
const db = connectToPg(connection)
|
|
23
|
+
|
|
24
|
+
const missingTables = []
|
|
25
|
+
|
|
26
|
+
for (const table of tables) {
|
|
27
|
+
const exists = await db.schema.hasTable(table)
|
|
28
|
+
if (!exists) {
|
|
29
|
+
missingTables.push(table)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (missingTables.length > 0) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`Missing the following tables: ${missingTables.join(', ')}. ` +
|
|
36
|
+
`Did you run migrations?`,
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, it, beforeAll, afterAll, expect } from 'vitest'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
startPostgres,
|
|
5
|
+
stopPostgres,
|
|
6
|
+
buildPostgresUri,
|
|
7
|
+
} from '../../src/postgresql/start-stop-postgres-docker.js'
|
|
8
|
+
import { connectToPg } from '../../src/postgresql/connect-to-pg.js'
|
|
9
|
+
import { validateSchema } from '../../src/postgresql/validate-schema.js'
|
|
10
|
+
|
|
11
|
+
const PG_OPTIONS = {
|
|
12
|
+
port: 5433,
|
|
13
|
+
containerName: 'postgres-validate-schema-test',
|
|
14
|
+
user: 'testuser',
|
|
15
|
+
pass: 'testpass',
|
|
16
|
+
db: 'testdb',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const DATABASE_URI = buildPostgresUri(PG_OPTIONS)
|
|
20
|
+
|
|
21
|
+
describe('validateSchema', () => {
|
|
22
|
+
beforeAll(async () => {
|
|
23
|
+
startPostgres(PG_OPTIONS)
|
|
24
|
+
|
|
25
|
+
const db = connectToPg(DATABASE_URI)
|
|
26
|
+
|
|
27
|
+
// create one table only
|
|
28
|
+
await db.schema.createTable('files', (table) => {
|
|
29
|
+
table.string('id').primary()
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
await db.destroy()
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
afterAll(() => {
|
|
36
|
+
stopPostgres(PG_OPTIONS.containerName)
|
|
37
|
+
})
|
|
38
|
+
|
|
39
|
+
it('does not throw when all required tables exist', async () => {
|
|
40
|
+
await expect(validateSchema(DATABASE_URI, ['files'])).resolves.not.toThrow()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('throws a single error listing missing tables', async () => {
|
|
44
|
+
await expect(
|
|
45
|
+
validateSchema(DATABASE_URI, ['files', 'documents', 'attachments']),
|
|
46
|
+
).rejects.toThrow('Missing the following tables: documents, attachments')
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
it('throws when all tables are missing', async () => {
|
|
50
|
+
await expect(
|
|
51
|
+
validateSchema(DATABASE_URI, ['missing_a', 'missing_b']),
|
|
52
|
+
).rejects.toThrow('Missing the following tables: missing_a, missing_b')
|
|
53
|
+
})
|
|
54
|
+
})
|