core-services-sdk 1.3.50 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "core-services-sdk",
3
- "version": "1.3.50",
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",
@@ -4,12 +4,7 @@ import { ID_PREFIXES } from './prefixes.js'
4
4
  /**
5
5
  * Generates a new ULID string.
6
6
  *
7
- * ULIDs are 26-character, lexicographically sortable identifiers.
8
- *
9
- * @returns {string} A new ULID.
10
- *
11
- * @example
12
- * generateId() // '01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
7
+ * @returns {string}
13
8
  */
14
9
  export const generateId = () => {
15
10
  return ulid()
@@ -18,222 +13,140 @@ export const generateId = () => {
18
13
  /**
19
14
  * Generates a unique ID with the given prefix.
20
15
  *
21
- * @param {string} prefix - A prefix string to prepend to the ULID.
22
- * @returns {string} A unique ID in the format `${prefix}_${ulid}`.
23
- *
24
- * @example
25
- * generatePrefixedId('usr') // 'usr_01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
16
+ * @param {string} prefix
17
+ * @returns {string}
26
18
  */
27
19
  export const generatePrefixedId = (prefix) => {
28
20
  return `${prefix}_${generateId()}`
29
21
  }
30
22
 
31
- /**
32
- * Generates a user ID with a `usr_` prefix.
33
- *
34
- * @returns {string} A user ID.
35
- */
23
+ /* =========================
24
+ * Core identity
25
+ * ========================= */
26
+
36
27
  export const generateUserId = () => generatePrefixedId(ID_PREFIXES.USER)
37
28
 
38
- /**
39
- * Generates a tenant ID with a `tnt_` prefix.
40
- *
41
- * @returns {string} A tenant ID.
42
- */
43
29
  export const generateTenantId = () => generatePrefixedId(ID_PREFIXES.TENANT)
44
30
 
45
- /**
46
- * Generates a permission ID with a `prm_` prefix.
47
- *
48
- * @returns {string} A permission ID.
49
- */
31
+ export const generateSessionId = () => generatePrefixedId(ID_PREFIXES.SESSION)
32
+
33
+ export const generateOnboardingId = () =>
34
+ generatePrefixedId(ID_PREFIXES.ONBOARDING)
35
+
36
+ /* =========================
37
+ * Authorization
38
+ * ========================= */
39
+
50
40
  export const generatePermissionId = () =>
51
41
  generatePrefixedId(ID_PREFIXES.PERMISSION)
52
42
 
53
- /**
54
- * Generates a correlation ID with a `crln_` prefix.
55
- *
56
- * @returns {string} A correlation ID.
57
- */
58
- export const generateCorrelationId = () =>
59
- generatePrefixedId(ID_PREFIXES.CORRELATION)
43
+ export const generateRoleId = () => generatePrefixedId(ID_PREFIXES.ROLE)
44
+
45
+ export const generateRolePermissionsId = () =>
46
+ generatePrefixedId(ID_PREFIXES.ROLE_PERMISSION)
60
47
 
61
- /**
62
- * Generates a verification ID with a `vrf_` prefix.
63
- *
64
- * @returns {string} A verification ID.
65
- */
66
48
  export const generateVerificationId = () =>
67
49
  generatePrefixedId(ID_PREFIXES.VERIFICATION)
68
50
 
69
- /**
70
- * Generates a role permissions ID with a `role_` prefix.
71
- *
72
- * @returns {string} A role permissions ID.
73
- */
74
- export const generateRolePermissionsId = () =>
75
- generatePrefixedId(ID_PREFIXES.ROLE_PERMISSIONS)
51
+ /* =========================
52
+ * Assets & uploads
53
+ * ========================= */
76
54
 
77
- /**
78
- * Generates an onboarding ID with a `onb_` prefix.
79
- *
80
- * @returns {string} An onboarding ID.
81
- */
82
- export const generateOnboardingId = () =>
83
- generatePrefixedId(ID_PREFIXES.ONBOARDING)
55
+ export const generateAssetId = () => generatePrefixedId(ID_PREFIXES.ASSET)
84
56
 
85
- /**
86
- * Generates a session ID with a `sess_` prefix.
87
- *
88
- * @returns {string} A session ID.
89
- */
90
- export const generateSessionId = () => generatePrefixedId(ID_PREFIXES.SESSION)
57
+ export const generateAssetUploadId = () =>
58
+ generatePrefixedId(ID_PREFIXES.ASSET_UPLOAD)
59
+
60
+ /* =========================
61
+ * Files (legacy)
62
+ * ========================= */
91
63
 
92
- /**
93
- * Generates a file ID with a `fil_` prefix.
94
- *
95
- * @returns {string} A file ID.
96
- */
97
64
  export const generateFileId = () => generatePrefixedId(ID_PREFIXES.FILE)
98
65
 
99
- /**
100
- * Generates an event ID with an `evt_` prefix.
101
- *
102
- * @returns {string} An event ID.
103
- */
66
+ /* =========================
67
+ * Accounting
68
+ * ========================= */
69
+
70
+ export const generateSupplierId = () => generatePrefixedId(ID_PREFIXES.SUPPLIER)
71
+
72
+ export const generateInvoiceId = () => generatePrefixedId(ID_PREFIXES.INVOICE)
73
+
74
+ export const generateInvoiceItemId = () =>
75
+ generatePrefixedId(ID_PREFIXES.INVOICE_ITEM)
76
+
77
+ export const generateInvoiceCorrectionId = () =>
78
+ generatePrefixedId(ID_PREFIXES.INVOICE_CORRECTION)
79
+
80
+ /* =========================
81
+ * AI / document processing
82
+ * ========================= */
83
+
84
+ export const generateDocumentDataId = () =>
85
+ generatePrefixedId(ID_PREFIXES.DOCUMENT_DATA)
86
+
87
+ /* =========================
88
+ * Bots / automation
89
+ * ========================= */
90
+
91
+ export const generateBotFlowId = () => generatePrefixedId(ID_PREFIXES.BOT_FLOW)
92
+
93
+ /* =========================
94
+ * Infra / messaging
95
+ * ========================= */
96
+
97
+ export const generateCorrelationId = () =>
98
+ generatePrefixedId(ID_PREFIXES.CORRELATION)
99
+
104
100
  export const generateEventId = () => generatePrefixedId(ID_PREFIXES.EVENT)
105
101
 
106
- /**
107
- * Generates a job ID with a `job_` prefix.
108
- *
109
- * @returns {string} A job ID.
110
- */
111
102
  export const generateJobId = () => generatePrefixedId(ID_PREFIXES.JOB)
112
103
 
113
- /**
114
- * Generates a task ID with a `task_` prefix.
115
- *
116
- * @returns {string} A task ID.
117
- */
118
104
  export const generateTaskId = () => generatePrefixedId(ID_PREFIXES.TASK)
119
105
 
120
- /**
121
- * Generates a queue ID with a `que_` prefix.
122
- *
123
- * @returns {string} A queue ID.
124
- */
125
106
  export const generateQueueId = () => generatePrefixedId(ID_PREFIXES.QUEUE)
126
107
 
127
- /**
128
- * Generates a message ID with a `msg_` prefix.
129
- *
130
- * @returns {string} A message ID.
131
- */
132
108
  export const generateMessageId = () => generatePrefixedId(ID_PREFIXES.MESSAGE)
133
109
 
134
- /**
135
- * Generates a notification ID with a `ntf_` prefix.
136
- *
137
- * @returns {string} A notification ID.
138
- */
110
+ /* =========================
111
+ * Communication
112
+ * ========================= */
113
+
114
+ export const generateEmailId = () => generatePrefixedId(ID_PREFIXES.EMAIL)
115
+
116
+ export const generateIncomingEmailId = () =>
117
+ generatePrefixedId(ID_PREFIXES.INCOMING_EMAIL)
118
+
119
+ export const generateImId = () => generatePrefixedId(ID_PREFIXES.IM)
120
+
139
121
  export const generateNotificationId = () =>
140
122
  generatePrefixedId(ID_PREFIXES.NOTIFICATION)
141
123
 
142
- /**
143
- * Generates a log ID with a `log_` prefix.
144
- *
145
- * @returns {string} A log ID.
146
- */
147
- export const generateLogId = () => generatePrefixedId(ID_PREFIXES.LOG)
124
+ /* =========================
125
+ * Observability
126
+ * ========================= */
148
127
 
149
- /**
150
- * Generates an audit ID with an `adt_` prefix.
151
- *
152
- * @returns {string} An audit ID.
153
- */
154
128
  export const generateAuditId = () => generatePrefixedId(ID_PREFIXES.AUDIT)
155
129
 
156
- /**
157
- * Generates a config ID with a `cfg_` prefix.
158
- *
159
- * @returns {string} A config ID.
160
- */
161
- export const generateConfigId = () => generatePrefixedId(ID_PREFIXES.CONFIG)
162
-
163
- /**
164
- * Generates a key ID with a `key_` prefix.
165
- *
166
- * @returns {string} A key ID.
167
- */
168
- export const generateKeyId = () => generatePrefixedId(ID_PREFIXES.KEY)
130
+ export const generateLogId = () => generatePrefixedId(ID_PREFIXES.LOG)
169
131
 
170
- /**
171
- * Generates a metric ID with a `met_` prefix.
172
- *
173
- * @returns {string} A metric ID.
174
- */
175
132
  export const generateMetricId = () => generatePrefixedId(ID_PREFIXES.METRIC)
176
133
 
177
- /**
178
- * Generates a tag ID with a `tag_` prefix.
179
- *
180
- * @returns {string} A tag ID.
181
- */
182
- export const generateTagId = () => generatePrefixedId(ID_PREFIXES.TAG)
134
+ /* =========================
135
+ * Security / config / misc
136
+ * ========================= */
137
+
138
+ export const generateKeyId = () => generatePrefixedId(ID_PREFIXES.KEY)
183
139
 
184
- /**
185
- * Generates a policy ID with a `plc_` prefix.
186
- *
187
- * @returns {string} A policy ID.
188
- */
189
140
  export const generatePolicyId = () => generatePrefixedId(ID_PREFIXES.POLICY)
190
141
 
191
- /**
192
- * Generates a profile ID with a `prf_` prefix.
193
- *
194
- * @returns {string} A profile ID.
195
- */
196
142
  export const generateProfileId = () => generatePrefixedId(ID_PREFIXES.PROFILE)
197
143
 
198
- /**
199
- * Generates a device ID with a `dev_` prefix.
200
- *
201
- * @returns {string} A device ID.
202
- */
203
144
  export const generateDeviceId = () => generatePrefixedId(ID_PREFIXES.DEVICE)
204
145
 
205
- /**
206
- * Generates an alert ID with an `alr_` prefix.
207
- *
208
- * @returns {string} An alert ID.
209
- */
210
146
  export const generateAlertId = () => generatePrefixedId(ID_PREFIXES.ALERT)
211
147
 
212
- /**
213
- * Generates a resource ID with a `res_` prefix.
214
- *
215
- * @returns {string} A resource ID.
216
- */
217
148
  export const generateResourceId = () => generatePrefixedId(ID_PREFIXES.RESOURCE)
218
149
 
219
- /**
220
- * Generates a resource ID with a `ieml_` prefix.
221
- *
222
- * @returns {string} An incoming email ID.
223
- */
224
- export const generateIncomingEmailId = () =>
225
- generatePrefixedId(ID_PREFIXES.INCOMING_EMAIL)
226
-
227
- /**
228
- * Generates a resource ID with a `eml_` prefix.
229
- *
230
- * @returns {string} An Email ID.
231
- */
232
- export const generateEmailId = () => generatePrefixedId(ID_PREFIXES.EMAIL)
150
+ export const generateTagId = () => generatePrefixedId(ID_PREFIXES.TAG)
233
151
 
234
- /**
235
- * Generates a resource ID with a `im_` prefix.
236
- *
237
- * @returns {string} An Instant Message ID.
238
- */
239
- export const generateImId = () => generatePrefixedId(ID_PREFIXES.IM)
152
+ export const generateConfigId = () => generatePrefixedId(ID_PREFIXES.CONFIG)
@@ -1,97 +1,93 @@
1
1
  /**
2
2
  * Mapping of entity types to their unique ID prefixes.
3
3
  *
4
- * These prefixes are prepended to ULIDs to create consistent and identifiable IDs across the system.
5
- * For example: 'usr_01HZY3M7K4FJ9A8Q4Y1ZB5NX3T'
4
+ * Prefixes are prepended to ULIDs to create readable,
5
+ * sortable, and easily identifiable IDs across the system.
6
+ *
7
+ * Example:
8
+ * ast_01HZY3M7K4FJ9A8Q4Y1ZB5NX3T
6
9
  *
7
10
  * @readonly
8
11
  * @enum {string}
9
12
  */
10
13
  export const ID_PREFIXES = Object.freeze({
11
- /** User entity ID prefix */
14
+ //////////////////////////////////////////////////////
15
+ // Core identity
16
+ //////////////////////////////////////////////////////
12
17
  USER: 'usr',
13
-
14
- /** Tenant entity ID prefix */
15
18
  TENANT: 'tnt',
19
+ SESSION: 'sess',
20
+ ONBOARDING: 'onb',
16
21
 
17
- /** Permission entity ID prefix */
22
+ //////////////////////////////////////////////////////
23
+ // Authorization & access
24
+ //////////////////////////////////////////////////////
25
+ ROLE: 'role',
18
26
  PERMISSION: 'prm',
19
-
20
- /** Correlation ID prefix (e.g., for tracing requests) */
21
- CORRELATION: 'crln',
22
-
23
- /** Verification entity ID prefix (e.g., email/phone code) */
27
+ ROLE_PERMISSION: 'rprm',
24
28
  VERIFICATION: 'vrf',
29
+ POLICY: 'plc',
30
+ PROFILE: 'prf',
31
+ DEVICE: 'dev',
25
32
 
26
- /** Role-permissions mapping ID prefix */
27
- ROLE_PERMISSIONS: 'role',
28
-
29
- /** Onboarding mapping ID prefix */
30
- ONBOARDING: 'onb',
31
-
32
- /** Session mapping ID prefix */
33
- SESSION: 'sess',
33
+ //////////////////////////////////////////////////////
34
+ // Assets & uploads
35
+ //////////////////////////////////////////////////////
36
+ ASSET: 'ast',
37
+ ASSET_UPLOAD: 'aupl',
34
38
 
35
- /** File mapping ID prefix */
39
+ // Legacy kept for backward compatibility
36
40
  FILE: 'fil',
37
41
 
38
- /** Event entity ID prefix */
42
+ //////////////////////////////////////////////////////
43
+ // Accounting domain
44
+ //////////////////////////////////////////////////////
45
+ SUPPLIER: 'sup',
46
+ INVOICE: 'inv',
47
+ INVOICE_ITEM: 'invi',
48
+ INVOICE_CORRECTION: 'invc',
49
+
50
+ //////////////////////////////////////////////////////
51
+ // AI / document processing
52
+ //////////////////////////////////////////////////////
53
+ DOCUMENT_DATA: 'docd',
54
+
55
+ //////////////////////////////////////////////////////
56
+ // Messaging / jobs / infra
57
+ //////////////////////////////////////////////////////
58
+ CORRELATION: 'crln',
39
59
  EVENT: 'evt',
40
-
41
- /** Job entity ID prefix */
42
60
  JOB: 'job',
43
-
44
- /** Task entity ID prefix */
45
61
  TASK: 'task',
46
-
47
- /** Queue entity ID prefix */
48
62
  QUEUE: 'que',
49
-
50
- /** Message entity ID prefix */
51
63
  MESSAGE: 'msg',
52
-
53
- /** Notification entity ID prefix */
54
64
  NOTIFICATION: 'ntf',
55
65
 
56
- /** Log entity ID prefix */
57
- LOG: 'log',
66
+ //////////////////////////////////////////////////////
67
+ // Communication
68
+ //////////////////////////////////////////////////////
69
+ EMAIL: 'eml',
70
+ INCOMING_EMAIL: 'ieml',
71
+ IM: 'im',
58
72
 
59
- /** Audit entity ID prefix */
73
+ //////////////////////////////////////////////////////
74
+ // Observability
75
+ //////////////////////////////////////////////////////
60
76
  AUDIT: 'adt',
61
-
62
- /** Config entity ID prefix */
63
- CONFIG: 'cfg',
64
-
65
- /** Key entity ID prefix */
66
- KEY: 'key',
67
-
68
- /** Metric entity ID prefix */
77
+ LOG: 'log',
69
78
  METRIC: 'met',
70
79
 
71
- /** Tag entity ID prefix */
80
+ //////////////////////////////////////////////////////
81
+ // Misc / config
82
+ //////////////////////////////////////////////////////
72
83
  TAG: 'tag',
73
-
74
- /** Policy entity ID prefix */
75
- POLICY: 'plc',
76
-
77
- /** Profile entity ID prefix */
78
- PROFILE: 'prf',
79
-
80
- /** Device entity ID prefix */
81
- DEVICE: 'dev',
82
-
83
- /** Alert entity ID prefix */
84
- ALERT: 'alr',
85
-
86
- /** Resource entity ID prefix */
84
+ CONFIG: 'cfg',
85
+ KEY: 'key',
87
86
  RESOURCE: 'res',
87
+ ALERT: 'alr',
88
88
 
89
- /** Incoming email ID prefix */
90
- INCOMING_EMAIL: 'ieml',
91
-
92
- /** Email ID prefix */
93
- EMAIL: 'eml',
94
-
95
- /** Instant Message ID prefix */
96
- IM: 'im',
89
+ //////////////////////////////////////////////////////
90
+ // Bots / automation flows
91
+ //////////////////////////////////////////////////////
92
+ BOT_FLOW: 'botf',
97
93
  })
package/src/index.js CHANGED
@@ -10,3 +10,4 @@ export * from './rabbit-mq/index.js'
10
10
  export * from './templates/index.js'
11
11
  export * from './util/index.js'
12
12
  export * from './instant-messages/index.js'
13
+ export * from './postgresql/index.js'
@@ -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,3 @@
1
+ export * from './connect-to-pg.js'
2
+ export * from './validate-schema.js'
3
+ export * from './start-stop-postgres-docker.js'
@@ -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
+ }
@@ -29,12 +29,25 @@ import {
29
29
  generateDeviceId,
30
30
  generateAlertId,
31
31
  generateResourceId,
32
+ generateRoleId,
33
+ generateAssetId,
34
+ generateAssetUploadId,
35
+ generateSupplierId,
36
+ generateInvoiceId,
37
+ generateInvoiceItemId,
38
+ generateInvoiceCorrectionId,
39
+ generateDocumentDataId,
40
+ generateEmailId,
41
+ generateIncomingEmailId,
42
+ generateImId,
43
+ generateBotFlowId,
32
44
  } from '../../src/ids/generators.js'
33
45
  import { ID_PREFIXES } from '../../src/ids/prefixes.js'
34
46
 
35
47
  // ULID is a 26-character Base32 string (no I, L, O, U).
36
48
  const ULID_REGEX = /^[0-9A-HJKMNP-TV-Z]{26}$/
37
49
 
50
+ // @ts-ignore
38
51
  const testPrefixFunction = (fn, expectedPrefix) => {
39
52
  const id = fn()
40
53
  expect(typeof id).toBe('string')
@@ -91,7 +104,7 @@ describe('generate*Id functions', () => {
91
104
  })
92
105
 
93
106
  it('generateRolePermissionsId returns ID with role_ prefix', () => {
94
- testPrefixFunction(generateRolePermissionsId, ID_PREFIXES.ROLE_PERMISSIONS)
107
+ testPrefixFunction(generateRolePermissionsId, ID_PREFIXES.ROLE_PERMISSION)
95
108
  })
96
109
 
97
110
  it('generateSessionId returns ID with sess_ prefix', () => {
@@ -169,4 +182,55 @@ describe('generate*Id functions', () => {
169
182
  it('generateResourceId returns ID with res_ prefix', () => {
170
183
  testPrefixFunction(generateResourceId, ID_PREFIXES.RESOURCE)
171
184
  })
185
+
186
+ it('generateRoleId returns ID with role_ prefix', () => {
187
+ testPrefixFunction(generateRoleId, ID_PREFIXES.ROLE)
188
+ })
189
+
190
+ it('generateAssetId returns ID with ast_ prefix', () => {
191
+ testPrefixFunction(generateAssetId, ID_PREFIXES.ASSET)
192
+ })
193
+
194
+ it('generateAssetUploadId returns ID with aupl_ prefix', () => {
195
+ testPrefixFunction(generateAssetUploadId, ID_PREFIXES.ASSET_UPLOAD)
196
+ })
197
+
198
+ it('generateSupplierId returns ID with sup_ prefix', () => {
199
+ testPrefixFunction(generateSupplierId, ID_PREFIXES.SUPPLIER)
200
+ })
201
+
202
+ it('generateInvoiceId returns ID with inv_ prefix', () => {
203
+ testPrefixFunction(generateInvoiceId, ID_PREFIXES.INVOICE)
204
+ })
205
+
206
+ it('generateInvoiceItemId returns ID with invi_ prefix', () => {
207
+ testPrefixFunction(generateInvoiceItemId, ID_PREFIXES.INVOICE_ITEM)
208
+ })
209
+
210
+ it('generateInvoiceCorrectionId returns ID with invc_ prefix', () => {
211
+ testPrefixFunction(
212
+ generateInvoiceCorrectionId,
213
+ ID_PREFIXES.INVOICE_CORRECTION,
214
+ )
215
+ })
216
+
217
+ it('generateDocumentDataId returns ID with docd_ prefix', () => {
218
+ testPrefixFunction(generateDocumentDataId, ID_PREFIXES.DOCUMENT_DATA)
219
+ })
220
+
221
+ it('generateEmailId returns ID with eml_ prefix', () => {
222
+ testPrefixFunction(generateEmailId, ID_PREFIXES.EMAIL)
223
+ })
224
+
225
+ it('generateIncomingEmailId returns ID with ieml_ prefix', () => {
226
+ testPrefixFunction(generateIncomingEmailId, ID_PREFIXES.INCOMING_EMAIL)
227
+ })
228
+
229
+ it('generateImId returns ID with im_ prefix', () => {
230
+ testPrefixFunction(generateImId, ID_PREFIXES.IM)
231
+ })
232
+
233
+ it('generateBotFlowId returns ID with botf_ prefix', () => {
234
+ testPrefixFunction(generateBotFlowId, ID_PREFIXES.BOT_FLOW)
235
+ })
172
236
  })
@@ -5,35 +5,44 @@ import { ID_PREFIXES } from '../../src/ids/prefixes.js'
5
5
  describe('ID_PREFIXES', () => {
6
6
  it('contains expected keys and values', () => {
7
7
  expect(ID_PREFIXES).toEqual({
8
- USER: 'usr',
9
- TENANT: 'tnt',
10
- PERMISSION: 'prm',
8
+ ALERT: 'alr',
9
+ ASSET: 'ast',
10
+ ASSET_UPLOAD: 'aupl',
11
+ AUDIT: 'adt',
12
+ BOT_FLOW: 'botf',
13
+ CONFIG: 'cfg',
11
14
  CORRELATION: 'crln',
12
- VERIFICATION: 'vrf',
13
- ROLE_PERMISSIONS: 'role',
14
- ONBOARDING: 'onb',
15
- SESSION: 'sess',
16
- FILE: 'fil',
15
+ DEVICE: 'dev',
16
+ DOCUMENT_DATA: 'docd',
17
+ EMAIL: 'eml',
17
18
  EVENT: 'evt',
19
+ FILE: 'fil',
20
+ IM: 'im',
21
+ INCOMING_EMAIL: 'ieml',
22
+ INVOICE: 'inv',
23
+ INVOICE_CORRECTION: 'invc',
24
+ INVOICE_ITEM: 'invi',
18
25
  JOB: 'job',
19
- TASK: 'task',
20
- QUEUE: 'que',
21
- MESSAGE: 'msg',
22
- NOTIFICATION: 'ntf',
23
- LOG: 'log',
24
- AUDIT: 'adt',
25
- CONFIG: 'cfg',
26
26
  KEY: 'key',
27
+ LOG: 'log',
28
+ MESSAGE: 'msg',
27
29
  METRIC: 'met',
28
- TAG: 'tag',
30
+ NOTIFICATION: 'ntf',
31
+ ONBOARDING: 'onb',
32
+ PERMISSION: 'prm',
29
33
  POLICY: 'plc',
30
34
  PROFILE: 'prf',
31
- DEVICE: 'dev',
32
- ALERT: 'alr',
35
+ QUEUE: 'que',
33
36
  RESOURCE: 'res',
34
- INCOMING_EMAIL: 'ieml',
35
- EMAIL: 'eml',
36
- IM: 'im',
37
+ ROLE: 'role',
38
+ ROLE_PERMISSION: 'rprm',
39
+ SESSION: 'sess',
40
+ SUPPLIER: 'sup',
41
+ TAG: 'tag',
42
+ TASK: 'task',
43
+ TENANT: 'tnt',
44
+ USER: 'usr',
45
+ VERIFICATION: 'vrf',
37
46
  })
38
47
  })
39
48
 
@@ -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
+ })