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 +3 -1
- package/src/ids/generators.js +86 -173
- package/src/ids/prefixes.js +61 -65
- 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/ids/generators.unit.test.js +65 -1
- package/tests/ids/prefixes.unit.test.js +30 -21
- 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/ids/generators.js
CHANGED
|
@@ -4,12 +4,7 @@ import { ID_PREFIXES } from './prefixes.js'
|
|
|
4
4
|
/**
|
|
5
5
|
* Generates a new ULID string.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
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
|
|
22
|
-
* @returns {string}
|
|
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
|
-
*
|
|
33
|
-
*
|
|
34
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
*
|
|
101
|
-
*
|
|
102
|
-
|
|
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
|
-
*
|
|
136
|
-
*
|
|
137
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
179
|
-
*
|
|
180
|
-
|
|
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)
|
package/src/ids/prefixes.js
CHANGED
|
@@ -1,97 +1,93 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Mapping of entity types to their unique ID prefixes.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
/** Session mapping ID prefix */
|
|
33
|
-
SESSION: 'sess',
|
|
33
|
+
//////////////////////////////////////////////////////
|
|
34
|
+
// Assets & uploads
|
|
35
|
+
//////////////////////////////////////////////////////
|
|
36
|
+
ASSET: 'ast',
|
|
37
|
+
ASSET_UPLOAD: 'aupl',
|
|
34
38
|
|
|
35
|
-
|
|
39
|
+
// Legacy – kept for backward compatibility
|
|
36
40
|
FILE: 'fil',
|
|
37
41
|
|
|
38
|
-
|
|
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
|
-
|
|
57
|
-
|
|
66
|
+
//////////////////////////////////////////////////////
|
|
67
|
+
// Communication
|
|
68
|
+
//////////////////////////////////////////////////////
|
|
69
|
+
EMAIL: 'eml',
|
|
70
|
+
INCOMING_EMAIL: 'ieml',
|
|
71
|
+
IM: 'im',
|
|
58
72
|
|
|
59
|
-
|
|
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
|
-
|
|
80
|
+
//////////////////////////////////////////////////////
|
|
81
|
+
// Misc / config
|
|
82
|
+
//////////////////////////////////////////////////////
|
|
72
83
|
TAG: 'tag',
|
|
73
|
-
|
|
74
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
@@ -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
|
+
}
|
|
@@ -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.
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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
|
-
|
|
30
|
+
NOTIFICATION: 'ntf',
|
|
31
|
+
ONBOARDING: 'onb',
|
|
32
|
+
PERMISSION: 'prm',
|
|
29
33
|
POLICY: 'plc',
|
|
30
34
|
PROFILE: 'prf',
|
|
31
|
-
|
|
32
|
-
ALERT: 'alr',
|
|
35
|
+
QUEUE: 'que',
|
|
33
36
|
RESOURCE: 'res',
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
+
})
|