@app-connect/core 1.7.36 → 1.7.38

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.
Files changed (287) hide show
  1. package/README.md +17 -16
  2. package/dist/connector/developerPortal.d.ts +6 -0
  3. package/{connector → dist/connector}/developerPortal.js +29 -12
  4. package/dist/connector/mock.d.ts +1 -0
  5. package/{connector → dist/connector}/mock.js +33 -17
  6. package/dist/connector/proxy/engine.d.ts +10 -0
  7. package/dist/connector/proxy/engine.js +222 -0
  8. package/dist/connector/proxy/index.d.ts +5 -0
  9. package/dist/connector/proxy/index.js +511 -0
  10. package/dist/connector/registry.d.ts +1 -0
  11. package/dist/connector/registry.js +243 -0
  12. package/dist/handlers/admin.d.ts +1 -0
  13. package/{handlers → dist/handlers}/admin.js +137 -78
  14. package/dist/handlers/appointment.d.ts +9 -0
  15. package/{handlers → dist/handlers}/appointment.js +58 -29
  16. package/dist/handlers/auth.d.ts +9 -0
  17. package/{handlers → dist/handlers}/auth.js +68 -40
  18. package/dist/handlers/calldown.d.ts +10 -0
  19. package/dist/handlers/calldown.js +134 -0
  20. package/dist/handlers/contact.d.ts +1 -0
  21. package/{handlers → dist/handlers}/contact.js +61 -39
  22. package/dist/handlers/disposition.d.ts +1 -0
  23. package/{handlers → dist/handlers}/disposition.js +20 -10
  24. package/dist/handlers/log.d.ts +1 -0
  25. package/{handlers → dist/handlers}/log.js +91 -124
  26. package/dist/handlers/managedAuth.d.ts +1 -0
  27. package/{handlers → dist/handlers}/managedAuth.js +134 -61
  28. package/dist/handlers/managedOAuth.d.ts +13 -0
  29. package/{handlers → dist/handlers}/managedOAuth.js +102 -30
  30. package/dist/handlers/plugin.d.ts +1 -0
  31. package/{handlers → dist/handlers}/plugin.js +59 -27
  32. package/dist/handlers/user.d.ts +1 -0
  33. package/{handlers → dist/handlers}/user.js +45 -27
  34. package/dist/index.d.ts +1 -0
  35. package/{index.js → dist/index.js} +548 -450
  36. package/dist/jest.config.d.ts +1 -0
  37. package/dist/jest.config.js +65 -0
  38. package/dist/lib/analytics.d.ts +4 -0
  39. package/{lib → dist/lib}/analytics.js +8 -6
  40. package/dist/lib/authSession.d.ts +5 -0
  41. package/{lib → dist/lib}/authSession.js +15 -43
  42. package/dist/lib/cacheCleanup.d.ts +4 -0
  43. package/{lib → dist/lib}/cacheCleanup.js +5 -17
  44. package/dist/lib/callLogComposer.d.ts +109 -0
  45. package/{lib → dist/lib}/callLogComposer.js +157 -197
  46. package/dist/lib/callLogLookup.d.ts +6 -0
  47. package/{lib → dist/lib}/callLogLookup.js +12 -34
  48. package/dist/lib/constants.d.ts +6 -0
  49. package/dist/lib/constants.js +9 -0
  50. package/dist/lib/debugTracer.d.ts +17 -0
  51. package/{lib → dist/lib}/debugTracer.js +7 -83
  52. package/dist/lib/encode.d.ts +3 -0
  53. package/dist/lib/encode.js +50 -0
  54. package/dist/lib/errorHandler.d.ts +7 -0
  55. package/dist/lib/errorHandler.js +161 -0
  56. package/dist/lib/generalErrorMessage.d.ts +7 -0
  57. package/{lib → dist/lib}/generalErrorMessage.js +6 -6
  58. package/dist/lib/jwt.d.ts +4 -0
  59. package/dist/lib/jwt.js +18 -0
  60. package/dist/lib/logger.d.ts +24 -0
  61. package/dist/lib/logger.js +126 -0
  62. package/dist/lib/migrateCallLogsSchema.d.ts +32 -0
  63. package/dist/lib/migrateCallLogsSchema.js +339 -0
  64. package/dist/lib/oauth.d.ts +4 -0
  65. package/{lib → dist/lib}/oauth.js +157 -160
  66. package/dist/lib/ringcentral.d.ts +24 -0
  67. package/dist/lib/ringcentral.js +226 -0
  68. package/dist/lib/s3ErrorLogReport.d.ts +4 -0
  69. package/{lib → dist/lib}/s3ErrorLogReport.js +11 -23
  70. package/dist/lib/sharedSMSComposer.d.ts +7 -0
  71. package/{lib → dist/lib}/sharedSMSComposer.js +21 -107
  72. package/dist/lib/util.d.ts +7 -0
  73. package/{lib → dist/lib}/util.js +9 -20
  74. package/dist/mcp/lib/oauthError.d.ts +1 -0
  75. package/dist/mcp/lib/oauthError.js +25 -0
  76. package/dist/mcp/lib/validator.d.ts +1 -0
  77. package/{mcp → dist/mcp}/lib/validator.js +16 -20
  78. package/dist/mcp/lib/widgetSessionToken.d.ts +1 -0
  79. package/dist/mcp/lib/widgetSessionToken.js +46 -0
  80. package/dist/mcp/mcpHandler.d.ts +8 -0
  81. package/{mcp → dist/mcp}/mcpHandler.js +98 -88
  82. package/dist/mcp/tools/cancelAppointment.d.ts +1 -0
  83. package/{mcp → dist/mcp}/tools/cancelAppointment.js +7 -16
  84. package/dist/mcp/tools/checkAuthStatus.d.ts +1 -0
  85. package/{mcp → dist/mcp}/tools/checkAuthStatus.js +21 -14
  86. package/dist/mcp/tools/confirmAppointment.d.ts +1 -0
  87. package/{mcp → dist/mcp}/tools/confirmAppointment.js +7 -16
  88. package/dist/mcp/tools/createAppointment.d.ts +1 -0
  89. package/{mcp → dist/mcp}/tools/createAppointment.js +7 -19
  90. package/dist/mcp/tools/createCallLog.d.ts +1 -0
  91. package/{mcp → dist/mcp}/tools/createCallLog.js +18 -29
  92. package/dist/mcp/tools/createContact.d.ts +1 -0
  93. package/{mcp → dist/mcp}/tools/createContact.js +8 -20
  94. package/dist/mcp/tools/createMessageLog.d.ts +1 -0
  95. package/{mcp → dist/mcp}/tools/createMessageLog.js +14 -26
  96. package/dist/mcp/tools/doAuth.d.ts +1 -0
  97. package/{mcp → dist/mcp}/tools/doAuth.js +17 -13
  98. package/dist/mcp/tools/findContactByName.d.ts +1 -0
  99. package/{mcp → dist/mcp}/tools/findContactByName.js +9 -15
  100. package/dist/mcp/tools/findContactByPhone.d.ts +1 -0
  101. package/{mcp → dist/mcp}/tools/findContactByPhone.js +8 -15
  102. package/dist/mcp/tools/getCallLog.d.ts +1 -0
  103. package/{mcp → dist/mcp}/tools/getCallLog.js +12 -21
  104. package/dist/mcp/tools/getGoogleFilePicker.d.ts +1 -0
  105. package/{mcp → dist/mcp}/tools/getGoogleFilePicker.js +9 -18
  106. package/dist/mcp/tools/getHelp.d.ts +1 -0
  107. package/{mcp → dist/mcp}/tools/getHelp.js +5 -6
  108. package/dist/mcp/tools/getPublicConnectors.d.ts +1 -0
  109. package/{mcp → dist/mcp}/tools/getPublicConnectors.js +19 -18
  110. package/dist/mcp/tools/getSessionInfo.d.ts +1 -0
  111. package/{mcp → dist/mcp}/tools/getSessionInfo.js +10 -16
  112. package/dist/mcp/tools/index.d.ts +8 -0
  113. package/dist/mcp/tools/index.js +50 -0
  114. package/dist/mcp/tools/listAppointments.d.ts +1 -0
  115. package/{mcp → dist/mcp}/tools/listAppointments.js +7 -19
  116. package/dist/mcp/tools/logout.d.ts +1 -0
  117. package/{mcp → dist/mcp}/tools/logout.js +16 -14
  118. package/dist/mcp/tools/rcGetCallLogs.d.ts +1 -0
  119. package/{mcp → dist/mcp}/tools/rcGetCallLogs.js +10 -8
  120. package/dist/mcp/tools/updateAppointment.d.ts +1 -0
  121. package/{mcp → dist/mcp}/tools/updateAppointment.js +17 -22
  122. package/dist/mcp/tools/updateCallLog.d.ts +1 -0
  123. package/{mcp → dist/mcp}/tools/updateCallLog.js +16 -26
  124. package/dist/models/accountDataModel.d.ts +3 -0
  125. package/dist/models/accountDataModel.js +39 -0
  126. package/dist/models/adminConfigModel.d.ts +2 -0
  127. package/dist/models/adminConfigModel.js +39 -0
  128. package/dist/models/cacheModel.d.ts +2 -0
  129. package/{models → dist/models}/cacheModel.js +7 -3
  130. package/dist/models/callDownListModel.d.ts +2 -0
  131. package/{models → dist/models}/callDownListModel.js +8 -4
  132. package/dist/models/callLogModel.d.ts +2 -0
  133. package/dist/models/callLogModel.js +43 -0
  134. package/dist/models/dynamo/connectorSchema.d.ts +2 -0
  135. package/dist/models/dynamo/connectorSchema.js +141 -0
  136. package/dist/models/dynamo/lockSchema.d.ts +2 -0
  137. package/{models → dist/models}/dynamo/lockSchema.js +5 -7
  138. package/dist/models/dynamo/noteCacheSchema.d.ts +2 -0
  139. package/{models → dist/models}/dynamo/noteCacheSchema.js +4 -6
  140. package/dist/models/llmSessionModel.d.ts +2 -0
  141. package/dist/models/llmSessionModel.js +21 -0
  142. package/dist/models/messageLogModel.d.ts +2 -0
  143. package/{models → dist/models}/messageLogModel.js +8 -4
  144. package/dist/models/sequelize.d.ts +2 -0
  145. package/dist/models/sequelize.js +68 -0
  146. package/dist/models/userModel.d.ts +2 -0
  147. package/dist/models/userModel.js +49 -0
  148. package/dist/package.json +100 -0
  149. package/dist/releaseNotes.json +1222 -0
  150. package/dist/tsconfig.json +14 -0
  151. package/dist/types/admin.d.ts +141 -0
  152. package/dist/types/analytics.d.ts +14 -0
  153. package/dist/types/appointment.d.ts +50 -0
  154. package/dist/types/auth.d.ts +137 -0
  155. package/dist/types/authSession.d.ts +35 -0
  156. package/dist/types/cacheCleanup.d.ts +12 -0
  157. package/dist/types/calldown.d.ts +54 -0
  158. package/dist/types/common.d.ts +46 -0
  159. package/dist/types/connector.d.ts +165 -0
  160. package/dist/types/contact.d.ts +125 -0
  161. package/dist/types/errorHandler.d.ts +60 -0
  162. package/dist/types/index.d.ts +76 -0
  163. package/dist/types/json.d.ts +9 -0
  164. package/dist/types/jwt.d.ts +8 -0
  165. package/dist/types/logging.d.ts +224 -0
  166. package/dist/types/managedAuth.d.ts +114 -0
  167. package/dist/types/managedOAuth.d.ts +59 -0
  168. package/dist/types/manifest.d.ts +120 -0
  169. package/dist/types/migration.d.ts +15 -0
  170. package/dist/types/plugin.d.ts +74 -0
  171. package/dist/types/ringcentral.d.ts +61 -0
  172. package/dist/types/s3ErrorLogReport.d.ts +16 -0
  173. package/dist/types/sharedSMSComposer.d.ts +105 -0
  174. package/dist/types/user.d.ts +46 -0
  175. package/dist/types/util.d.ts +7 -0
  176. package/package.json +34 -6
  177. package/releaseNotes.json +13 -1
  178. package/.env.test +0 -5
  179. package/connector/proxy/engine.js +0 -165
  180. package/connector/proxy/index.js +0 -500
  181. package/connector/registry.js +0 -252
  182. package/docs/README.md +0 -50
  183. package/docs/architecture.md +0 -94
  184. package/docs/connectors.md +0 -116
  185. package/docs/handlers.md +0 -128
  186. package/docs/libraries.md +0 -98
  187. package/docs/models.md +0 -146
  188. package/docs/routes.md +0 -119
  189. package/docs/tests.md +0 -73
  190. package/handlers/calldown.js +0 -99
  191. package/jest.config.js +0 -57
  192. package/lib/constants.js +0 -9
  193. package/lib/encode.js +0 -30
  194. package/lib/errorHandler.js +0 -218
  195. package/lib/jwt.js +0 -18
  196. package/lib/logger.js +0 -190
  197. package/lib/migrateCallLogsSchema.js +0 -237
  198. package/lib/ringcentral.js +0 -267
  199. package/mcp/README.md +0 -420
  200. package/mcp/tools/index.js +0 -51
  201. package/mcp/ui/App/App.tsx +0 -358
  202. package/mcp/ui/App/components/AuthInfoForm.tsx +0 -113
  203. package/mcp/ui/App/components/AuthSuccess.tsx +0 -22
  204. package/mcp/ui/App/components/ConnectorList.tsx +0 -82
  205. package/mcp/ui/App/components/DebugPanel.tsx +0 -43
  206. package/mcp/ui/App/components/OAuthConnect.tsx +0 -270
  207. package/mcp/ui/App/lib/callTool.ts +0 -130
  208. package/mcp/ui/App/lib/debugLog.ts +0 -41
  209. package/mcp/ui/App/lib/developerPortal.ts +0 -111
  210. package/mcp/ui/App/main.css +0 -6
  211. package/mcp/ui/App/root.tsx +0 -13
  212. package/mcp/ui/dist/index.html +0 -53
  213. package/mcp/ui/vite.config.ts +0 -16
  214. package/models/accountDataModel.js +0 -34
  215. package/models/adminConfigModel.js +0 -35
  216. package/models/callLogModel.js +0 -39
  217. package/models/dynamo/connectorSchema.js +0 -146
  218. package/models/llmSessionModel.js +0 -17
  219. package/models/sequelize.js +0 -17
  220. package/models/userModel.js +0 -45
  221. package/test/connector/developerPortal.test.js +0 -166
  222. package/test/connector/mock.test.js +0 -131
  223. package/test/connector/proxy/engine.test.js +0 -211
  224. package/test/connector/proxy/index.test.js +0 -525
  225. package/test/connector/proxy/sample.json +0 -162
  226. package/test/connector/registry.test.js +0 -416
  227. package/test/handlers/admin.test.js +0 -960
  228. package/test/handlers/appointment.test.js +0 -260
  229. package/test/handlers/auth.test.js +0 -1096
  230. package/test/handlers/calldown.test.js +0 -310
  231. package/test/handlers/contact.test.js +0 -1014
  232. package/test/handlers/disposition.test.js +0 -396
  233. package/test/handlers/log.test.js +0 -1847
  234. package/test/handlers/managedAuth.test.js +0 -458
  235. package/test/handlers/managedOAuth.test.js +0 -262
  236. package/test/handlers/plugin.test.js +0 -429
  237. package/test/handlers/user.test.js +0 -381
  238. package/test/index.test.js +0 -301
  239. package/test/lib/analytics.test.js +0 -146
  240. package/test/lib/authSession.test.js +0 -173
  241. package/test/lib/cacheCleanup.test.js +0 -127
  242. package/test/lib/callLogComposer.test.js +0 -1231
  243. package/test/lib/debugTracer.test.js +0 -328
  244. package/test/lib/encode.test.js +0 -59
  245. package/test/lib/errorHandler.test.js +0 -246
  246. package/test/lib/generalErrorMessage.test.js +0 -82
  247. package/test/lib/jwt.test.js +0 -176
  248. package/test/lib/logger.test.js +0 -206
  249. package/test/lib/oauth.test.js +0 -359
  250. package/test/lib/ringcentral.test.js +0 -467
  251. package/test/lib/s3ErrorLogReport.test.js +0 -187
  252. package/test/lib/sharedSMSComposer.test.js +0 -1084
  253. package/test/lib/util.test.js +0 -329
  254. package/test/mcp/mcpHandler.test.js +0 -73
  255. package/test/mcp/mcpHandlerMore.test.js +0 -384
  256. package/test/mcp/tools/appointmentTools.test.js +0 -362
  257. package/test/mcp/tools/checkAuthStatus.test.js +0 -83
  258. package/test/mcp/tools/createCallLog.test.js +0 -436
  259. package/test/mcp/tools/createContact.test.js +0 -58
  260. package/test/mcp/tools/createMessageLog.test.js +0 -595
  261. package/test/mcp/tools/doAuth.test.js +0 -113
  262. package/test/mcp/tools/findContactByName.test.js +0 -275
  263. package/test/mcp/tools/findContactByPhone.test.js +0 -296
  264. package/test/mcp/tools/getCallLog.test.js +0 -298
  265. package/test/mcp/tools/getGoogleFilePicker.test.js +0 -281
  266. package/test/mcp/tools/getPublicConnectors.test.js +0 -107
  267. package/test/mcp/tools/getSessionInfo.test.js +0 -127
  268. package/test/mcp/tools/logout.test.js +0 -233
  269. package/test/mcp/tools/rcGetCallLogs.test.js +0 -56
  270. package/test/mcp/tools/updateCallLog.test.js +0 -360
  271. package/test/models/accountDataModel.test.js +0 -98
  272. package/test/models/callDownListModel.test.js +0 -125
  273. package/test/models/dynamo/connectorSchema.test.js +0 -189
  274. package/test/models/dynamo/lockSchema.test.js +0 -37
  275. package/test/models/dynamo/noteCacheSchema.test.js +0 -45
  276. package/test/models/llmSessionModel.test.js +0 -91
  277. package/test/models/models.test.js +0 -660
  278. package/test/routes/calldownRoutes.test.js +0 -224
  279. package/test/routes/coreRouterBroadRoutes.test.js +0 -855
  280. package/test/routes/dispositionRoutes.test.js +0 -192
  281. package/test/routes/managedAuthRoutes.test.js +0 -386
  282. package/test/routes/pluginRoutes.test.js +0 -262
  283. package/test/setup.js +0 -178
  284. /package/{mcp → dist/mcp}/ui/index.html +0 -0
  285. /package/{mcp → dist/mcp}/ui/package-lock.json +0 -0
  286. /package/{mcp → dist/mcp}/ui/package.json +0 -0
  287. /package/{mcp → dist/mcp}/ui/tsconfig.json +0 -0
@@ -0,0 +1,126 @@
1
+ "use strict";
2
+ const util = require('util');
3
+ const LOG_LEVELS = {
4
+ ERROR: 0,
5
+ WARN: 1,
6
+ INFO: 2,
7
+ DEBUG: 3
8
+ };
9
+ const COLORS = {
10
+ ERROR: '\x1b[31m',
11
+ WARN: '\x1b[33m',
12
+ INFO: '\x1b[36m',
13
+ DEBUG: '\x1b[90m',
14
+ RESET: '\x1b[0m'
15
+ };
16
+ class Logger {
17
+ constructor(options = {}) {
18
+ this.level = this._getLogLevel(options.level || process.env.LOG_LEVEL || 'INFO');
19
+ this.isProd = process.env.NODE_ENV === 'production';
20
+ this.enableColors = !this.isProd && Boolean(process.stdout.isTTY);
21
+ }
22
+ _getLogLevel(levelName) {
23
+ const upperLevel = levelName.toUpperCase();
24
+ return LOG_LEVELS[upperLevel] !== undefined ? LOG_LEVELS[upperLevel] : LOG_LEVELS.INFO;
25
+ }
26
+ _shouldLog(level) {
27
+ return LOG_LEVELS[level] <= this.level;
28
+ }
29
+ _formatMessage(level, message, context = {}) {
30
+ const timestamp = new Date().toISOString();
31
+ if (this.isProd) {
32
+ return JSON.stringify({
33
+ timestamp,
34
+ level,
35
+ message,
36
+ ...context
37
+ });
38
+ }
39
+ const color = this.enableColors ? COLORS[level] : '';
40
+ const reset = this.enableColors ? COLORS.RESET : '';
41
+ const contextStr = Object.keys(context).length > 0
42
+ ? '\n' + util.inspect(context, { depth: 4, colors: this.enableColors })
43
+ : '';
44
+ return `${color}[${timestamp}] [${level}]${reset} ${message}${contextStr}`;
45
+ }
46
+ _log(level, message, context = {}) {
47
+ if (!this._shouldLog(level)) {
48
+ return;
49
+ }
50
+ const formattedMessage = this._formatMessage(level, message, context);
51
+ if (level === 'ERROR' || level === 'WARN') {
52
+ console.error(formattedMessage);
53
+ }
54
+ else {
55
+ console.log(formattedMessage);
56
+ }
57
+ }
58
+ error(message, context = {}) {
59
+ let enrichedContext = context;
60
+ if (context.error instanceof Error) {
61
+ const { error, ...rest } = context;
62
+ const response = error.response;
63
+ enrichedContext = {
64
+ ...rest,
65
+ errorMessage: error.message,
66
+ errorStack: error.stack,
67
+ errorResponse: response?.data,
68
+ errorStatus: response?.status
69
+ };
70
+ }
71
+ this._log('ERROR', message, enrichedContext);
72
+ }
73
+ warn(message, context = {}) {
74
+ this._log('WARN', message, context);
75
+ }
76
+ info(message, context = {}) {
77
+ this._log('INFO', message, context);
78
+ }
79
+ debug(message, context = {}) {
80
+ this._log('DEBUG', message, context);
81
+ }
82
+ child(defaultContext = {}) {
83
+ return {
84
+ error: (message, context = {}) => this.error(message, { ...defaultContext, ...context }),
85
+ warn: (message, context = {}) => this.warn(message, { ...defaultContext, ...context }),
86
+ info: (message, context = {}) => this.info(message, { ...defaultContext, ...context }),
87
+ debug: (message, context = {}) => this.debug(message, { ...defaultContext, ...context }),
88
+ child: (additionalContext = {}) => this.child({ ...defaultContext, ...additionalContext })
89
+ };
90
+ }
91
+ logApiRequest({ method, url, status, duration, platform, error }) {
92
+ const context = {
93
+ method,
94
+ url,
95
+ status,
96
+ duration: duration ? `${duration}ms` : undefined,
97
+ platform
98
+ };
99
+ if (error) {
100
+ this.error('API request failed', { ...context, error });
101
+ }
102
+ else if (status !== undefined && status >= 400) {
103
+ this.warn('API request returned error status', context);
104
+ }
105
+ else {
106
+ this.debug('API request completed', context);
107
+ }
108
+ }
109
+ logDatabaseQuery({ operation, table, duration, error }) {
110
+ const context = {
111
+ operation,
112
+ table,
113
+ duration: duration ? `${duration}ms` : undefined
114
+ };
115
+ if (error) {
116
+ this.error('Database query failed', { ...context, error });
117
+ }
118
+ else {
119
+ this.debug('Database query completed', context);
120
+ }
121
+ }
122
+ }
123
+ const logger = new Logger();
124
+ logger.Logger = Logger;
125
+ logger.LOG_LEVELS = LOG_LEVELS;
126
+ module.exports = logger;
@@ -0,0 +1,32 @@
1
+ import type { MigrationLogger, SequelizeLike, SequelizeTransactionLike, TableDescription } from '../types';
2
+ declare function findColumnKey(tableDescription: TableDescription, name: string): string | undefined;
3
+ declare function sqliteCallLogsPkIncludesExtension(sequelize: SequelizeLike, options?: Record<string, unknown>): Promise<boolean>;
4
+ declare function sqliteCallLogsPkIncludesHashedExtension(sequelize: SequelizeLike, options?: Record<string, unknown>): Promise<boolean>;
5
+ /**
6
+ * SQLite cannot change composite PK via Sequelize addConstraint (it appends a second PRIMARY KEY).
7
+ * Rebuild the table with the current call-log identity primary key and copy rows.
8
+ */
9
+ declare function migrateCallLogsIdentitySqlite(sequelize: SequelizeLike, transaction: SequelizeTransactionLike): Promise<void>;
10
+ declare function migrateCallLogsExtensionNumberSqlite(sequelize: SequelizeLike, transaction: SequelizeTransactionLike): Promise<void>;
11
+ declare function ensureCallLogsHashedExtensionIdSchema(sequelize: SequelizeLike): Promise<void>;
12
+ /**
13
+ * Online (non-blocking) Postgres migration.
14
+ *
15
+ * Adds the hashedExtensionId column and rebuilds the primary key to the call-log
16
+ * identity WITHOUT holding an ACCESS EXCLUSIVE lock for the duration of the index
17
+ * build. This is the safe path for large production tables:
18
+ * - ADD COLUMN ... NOT NULL DEFAULT '' is metadata-only on PG 11+.
19
+ * - CREATE UNIQUE INDEX CONCURRENTLY builds the index while reads/writes continue.
20
+ * - ADD PRIMARY KEY USING INDEX attaches the prebuilt index with only a brief lock.
21
+ *
22
+ * Runs on a single dedicated pooled connection so the advisory lock and the
23
+ * (necessarily non-transactional) CONCURRENTLY statements share one backend session.
24
+ */
25
+ declare function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize: SequelizeLike, logger?: MigrationLogger): Promise<void>;
26
+ /**
27
+ * Automatic entry point. Safe to call fire-and-forget off the startup critical path:
28
+ * it never throws (failures are logged and retried on the next boot) and, on Postgres,
29
+ * never blocks readiness or live traffic.
30
+ */
31
+ declare function runCallLogsSchemaMigration(sequelize: SequelizeLike, logger?: MigrationLogger): Promise<void>;
32
+ export { findColumnKey, migrateCallLogsExtensionNumberSqlite, migrateCallLogsIdentitySqlite, sqliteCallLogsPkIncludesExtension, sqliteCallLogsPkIncludesHashedExtension, ensureCallLogsHashedExtensionIdSchema, ensureCallLogsHashedExtensionIdSchemaPostgresOnline, runCallLogsSchemaMigration };
@@ -0,0 +1,339 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findColumnKey = findColumnKey;
4
+ exports.migrateCallLogsExtensionNumberSqlite = migrateCallLogsExtensionNumberSqlite;
5
+ exports.migrateCallLogsIdentitySqlite = migrateCallLogsIdentitySqlite;
6
+ exports.sqliteCallLogsPkIncludesExtension = sqliteCallLogsPkIncludesExtension;
7
+ exports.sqliteCallLogsPkIncludesHashedExtension = sqliteCallLogsPkIncludesHashedExtension;
8
+ exports.ensureCallLogsHashedExtensionIdSchema = ensureCallLogsHashedExtensionIdSchema;
9
+ exports.ensureCallLogsHashedExtensionIdSchemaPostgresOnline = ensureCallLogsHashedExtensionIdSchemaPostgresOnline;
10
+ exports.runCallLogsSchemaMigration = runCallLogsSchemaMigration;
11
+ const Sequelize = require('sequelize');
12
+ const CALL_LOGS_TABLE = 'callLogs';
13
+ const HASHED_EXTENSION_ID_COLUMN = 'hashedExtensionId';
14
+ const CALL_LOG_IDENTITY_PK_COLUMNS = ['id', 'sessionId', 'extensionNumber', HASHED_EXTENSION_ID_COLUMN];
15
+ function findColumnKey(tableDescription, name) {
16
+ const lower = name.toLowerCase();
17
+ return Object.keys(tableDescription).find((k) => k.toLowerCase() === lower);
18
+ }
19
+ async function fetchCallLogsCreateSqlSqlite(sequelize, options = {}) {
20
+ const rows = await sequelize.query("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'callLogs'", { type: Sequelize.QueryTypes.SELECT, ...options });
21
+ return rows[0]?.sql ?? null;
22
+ }
23
+ function callLogsCreateSqlClaimsPkColumns(createSql, columns) {
24
+ if (!createSql || typeof createSql !== 'string') {
25
+ return false;
26
+ }
27
+ const pkRegex = /PRIMARY\s+KEY\s*\(([^)]+)\)/gi;
28
+ let m;
29
+ while ((m = pkRegex.exec(createSql)) !== null) {
30
+ const pkColumns = m[1]
31
+ .split(',')
32
+ .map((column) => column.replace(/["'`\[\]\s]/g, '').toLowerCase());
33
+ if (columns.every((column) => pkColumns.includes(column.toLowerCase()))) {
34
+ return true;
35
+ }
36
+ }
37
+ return false;
38
+ }
39
+ function callLogsCreateSqlClaimsExtensionPk(createSql) {
40
+ return callLogsCreateSqlClaimsPkColumns(createSql, ['extensionNumber']);
41
+ }
42
+ async function sqliteCallLogsPkIncludesExtension(sequelize, options = {}) {
43
+ const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
44
+ return callLogsCreateSqlClaimsExtensionPk(sql);
45
+ }
46
+ async function sqliteCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
47
+ const sql = await fetchCallLogsCreateSqlSqlite(sequelize, options);
48
+ return callLogsCreateSqlClaimsPkColumns(sql, CALL_LOG_IDENTITY_PK_COLUMNS);
49
+ }
50
+ async function describeCallLogsTable(sequelize, options = {}) {
51
+ return sequelize.getQueryInterface().describeTable(CALL_LOGS_TABLE, options);
52
+ }
53
+ /**
54
+ * SQLite cannot change composite PK via Sequelize addConstraint (it appends a second PRIMARY KEY).
55
+ * Rebuild the table with the current call-log identity primary key and copy rows.
56
+ */
57
+ async function migrateCallLogsIdentitySqlite(sequelize, transaction) {
58
+ const qg = sequelize.getQueryInterface().queryGenerator;
59
+ const qi = sequelize.getQueryInterface();
60
+ const q = (id) => qg.quoteIdentifier(id);
61
+ const tmpName = `callLogs_mig_legacy_${Date.now()}`;
62
+ const mainTableSql = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
63
+ const tmpTableSql = qg.quoteTable({ tableName: tmpName });
64
+ await sequelize.query(`ALTER TABLE ${mainTableSql} RENAME TO ${qg.quoteIdentifier(tmpName)};`, { transaction });
65
+ await sequelize.query(`
66
+ CREATE TABLE ${mainTableSql} (
67
+ ${q('id')} VARCHAR(255) NOT NULL,
68
+ ${q('sessionId')} VARCHAR(255) NOT NULL,
69
+ ${q('extensionNumber')} VARCHAR(255) NOT NULL DEFAULT '',
70
+ ${q(HASHED_EXTENSION_ID_COLUMN)} VARCHAR(255) NOT NULL DEFAULT '',
71
+ ${q('platform')} VARCHAR(255),
72
+ ${q('thirdPartyLogId')} VARCHAR(255),
73
+ ${q('userId')} VARCHAR(255),
74
+ ${q('contactId')} VARCHAR(255),
75
+ ${q('createdAt')} DATETIME NOT NULL,
76
+ ${q('updatedAt')} DATETIME NOT NULL,
77
+ PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map(q).join(', ')})
78
+ );
79
+ `, { transaction });
80
+ const oldDesc = await qi.describeTable(tmpName, { transaction });
81
+ const targetCols = [
82
+ 'id',
83
+ 'sessionId',
84
+ 'extensionNumber',
85
+ HASHED_EXTENSION_ID_COLUMN,
86
+ 'platform',
87
+ 'thirdPartyLogId',
88
+ 'userId',
89
+ 'contactId',
90
+ 'createdAt',
91
+ 'updatedAt',
92
+ ];
93
+ const missingRequired = targetCols.filter((c) => findColumnKey(oldDesc, c) == null && c !== 'extensionNumber' && c !== HASHED_EXTENSION_ID_COLUMN);
94
+ if (missingRequired.length) {
95
+ throw new Error(`unexpected callLogs schema before migration (missing columns: ${missingRequired.join(', ')})`);
96
+ }
97
+ const insertCols = targetCols.map(q).join(', ');
98
+ const selectExprs = targetCols
99
+ .map((col) => {
100
+ const key = findColumnKey(oldDesc, col);
101
+ if (key != null) {
102
+ return q(key);
103
+ }
104
+ return `CAST('' AS VARCHAR(255))`;
105
+ })
106
+ .join(', ');
107
+ await sequelize.query(`INSERT INTO ${mainTableSql} (${insertCols}) SELECT ${selectExprs} FROM ${tmpTableSql};`, { transaction });
108
+ await sequelize.query(`DROP TABLE ${tmpTableSql};`, { transaction });
109
+ }
110
+ async function migrateCallLogsExtensionNumberSqlite(sequelize, transaction) {
111
+ return migrateCallLogsIdentitySqlite(sequelize, transaction);
112
+ }
113
+ async function getPostgresPrimaryKeyConstraint(sequelize, options = {}) {
114
+ const constraints = await sequelize.query(`
115
+ SELECT tc.constraint_name AS "constraintName"
116
+ FROM information_schema.table_constraints tc
117
+ WHERE tc.table_name = :tableName
118
+ AND tc.constraint_type = 'PRIMARY KEY'
119
+ LIMIT 1
120
+ `, {
121
+ type: Sequelize.QueryTypes.SELECT,
122
+ replacements: { tableName: CALL_LOGS_TABLE },
123
+ ...options,
124
+ });
125
+ return constraints[0]?.constraintName ?? null;
126
+ }
127
+ async function postgresCallLogsPkIncludesHashedExtension(sequelize, options = {}) {
128
+ const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, options);
129
+ if (!pkConstraintName) {
130
+ return false;
131
+ }
132
+ const rows = await sequelize.query(`
133
+ SELECT kcu.column_name AS "columnName"
134
+ FROM information_schema.key_column_usage kcu
135
+ WHERE kcu.table_name = :tableName
136
+ AND kcu.constraint_name = :constraintName
137
+ ORDER BY kcu.ordinal_position
138
+ `, {
139
+ type: Sequelize.QueryTypes.SELECT,
140
+ replacements: { tableName: CALL_LOGS_TABLE, constraintName: pkConstraintName },
141
+ ...options,
142
+ });
143
+ const pkColumns = rows.map((row) => row.columnName.toLowerCase());
144
+ return CALL_LOG_IDENTITY_PK_COLUMNS.every((column) => pkColumns.includes(column.toLowerCase()));
145
+ }
146
+ async function migrateCallLogsIdentityPostgres(sequelize, transaction) {
147
+ const qi = sequelize.getQueryInterface();
148
+ const qg = qi.queryGenerator;
149
+ const table = qg.quoteTable({ tableName: CALL_LOGS_TABLE });
150
+ const pkConstraintName = await getPostgresPrimaryKeyConstraint(sequelize, { transaction });
151
+ if (pkConstraintName) {
152
+ await sequelize.query(`ALTER TABLE ${table} DROP CONSTRAINT ${qg.quoteIdentifier(pkConstraintName)};`, { transaction });
153
+ }
154
+ await sequelize.query(`ALTER TABLE ${table} ADD PRIMARY KEY (${CALL_LOG_IDENTITY_PK_COLUMNS.map((column) => qg.quoteIdentifier(column)).join(', ')});`, { transaction });
155
+ }
156
+ async function ensureCallLogsHashedExtensionIdSchema(sequelize) {
157
+ const qi = sequelize.getQueryInterface();
158
+ const dialect = sequelize.getDialect();
159
+ let desc = await describeCallLogsTable(sequelize);
160
+ if (findColumnKey(desc, HASHED_EXTENSION_ID_COLUMN) == null) {
161
+ await qi.addColumn(CALL_LOGS_TABLE, HASHED_EXTENSION_ID_COLUMN, {
162
+ type: Sequelize.STRING,
163
+ allowNull: false,
164
+ defaultValue: '',
165
+ });
166
+ desc = await describeCallLogsTable(sequelize);
167
+ }
168
+ if (dialect === 'sqlite') {
169
+ const hasIdentityPk = await sqliteCallLogsPkIncludesHashedExtension(sequelize);
170
+ if (!hasIdentityPk) {
171
+ await sequelize.transaction(async (transaction) => {
172
+ await migrateCallLogsIdentitySqlite(sequelize, transaction);
173
+ });
174
+ }
175
+ return;
176
+ }
177
+ if (dialect === 'postgres') {
178
+ const hasIdentityPk = await postgresCallLogsPkIncludesHashedExtension(sequelize);
179
+ if (!hasIdentityPk) {
180
+ await sequelize.transaction(async (transaction) => {
181
+ await migrateCallLogsIdentityPostgres(sequelize, transaction);
182
+ });
183
+ }
184
+ }
185
+ }
186
+ // Dedicated name for the online-built unique index that becomes the new PK.
187
+ const IDENTITY_INDEX_NAME = 'callLogs_identity_pk';
188
+ // Stable application-wide key for the Postgres advisory lock that serializes the
189
+ // migration across concurrently-booting instances. Any fixed integer works as long
190
+ // as it is unique within the app; keep it constant so every instance agrees on it.
191
+ const CALL_LOGS_MIGRATION_LOCK_KEY = 4972001;
192
+ async function postgresPrimaryKeyNameOnConnection(client) {
193
+ const { rows } = await client.query(`SELECT conname FROM pg_constraint
194
+ WHERE conrelid = '"${CALL_LOGS_TABLE}"'::regclass AND contype = 'p' LIMIT 1`);
195
+ return rows[0]?.conname ?? null;
196
+ }
197
+ async function postgresPkIncludesIdentityOnConnection(client) {
198
+ const { rows } = await client.query(`SELECT kcu.column_name AS col
199
+ FROM information_schema.table_constraints tc
200
+ JOIN information_schema.key_column_usage kcu
201
+ ON kcu.constraint_name = tc.constraint_name
202
+ AND kcu.table_name = tc.table_name
203
+ WHERE tc.table_name = $1 AND tc.constraint_type = 'PRIMARY KEY'`, [CALL_LOGS_TABLE]);
204
+ const cols = rows.map((r) => String(r.col).toLowerCase());
205
+ return CALL_LOG_IDENTITY_PK_COLUMNS.every((c) => cols.includes(c.toLowerCase()));
206
+ }
207
+ async function postgresColumnIsNullableOnConnection(client, column) {
208
+ const { rows } = await client.query(`SELECT is_nullable FROM information_schema.columns
209
+ WHERE table_name = $1 AND column_name = $2`, [CALL_LOGS_TABLE, column]);
210
+ return rows[0]?.is_nullable === 'YES';
211
+ }
212
+ // 'absent' | 'valid' | 'invalid' - an INVALID index is the residue of a
213
+ // CONCURRENTLY build that was interrupted (process killed, connection dropped).
214
+ async function postgresIdentityIndexState(client) {
215
+ const { rows } = await client.query(`SELECT (i.indisvalid AND i.indisready) AS ok
216
+ FROM pg_class c
217
+ JOIN pg_index i ON i.indexrelid = c.oid
218
+ WHERE c.relname = $1`, [IDENTITY_INDEX_NAME]);
219
+ if (rows.length === 0)
220
+ return 'absent';
221
+ return rows[0].ok ? 'valid' : 'invalid';
222
+ }
223
+ /**
224
+ * Online (non-blocking) Postgres migration.
225
+ *
226
+ * Adds the hashedExtensionId column and rebuilds the primary key to the call-log
227
+ * identity WITHOUT holding an ACCESS EXCLUSIVE lock for the duration of the index
228
+ * build. This is the safe path for large production tables:
229
+ * - ADD COLUMN ... NOT NULL DEFAULT '' is metadata-only on PG 11+.
230
+ * - CREATE UNIQUE INDEX CONCURRENTLY builds the index while reads/writes continue.
231
+ * - ADD PRIMARY KEY USING INDEX attaches the prebuilt index with only a brief lock.
232
+ *
233
+ * Runs on a single dedicated pooled connection so the advisory lock and the
234
+ * (necessarily non-transactional) CONCURRENTLY statements share one backend session.
235
+ */
236
+ async function ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, logger) {
237
+ // Cheap pre-check on a pooled connection; skip entirely once migrated.
238
+ if (await postgresCallLogsPkIncludesHashedExtension(sequelize)) {
239
+ return;
240
+ }
241
+ const connectionManager = sequelize.connectionManager;
242
+ const client = await connectionManager.getConnection({ type: 'write' });
243
+ let lockAcquired = false;
244
+ try {
245
+ const lockRes = await client.query(`SELECT pg_try_advisory_lock(${CALL_LOGS_MIGRATION_LOCK_KEY}) AS locked`);
246
+ lockAcquired = lockRes.rows[0]?.locked === true;
247
+ if (!lockAcquired) {
248
+ logger?.info?.('[callLogs migration] another instance is migrating; skipping this run');
249
+ return;
250
+ }
251
+ // Re-check under the lock - another instance may have finished between the
252
+ // pre-check and acquiring the lock.
253
+ if (await postgresPkIncludesIdentityOnConnection(client)) {
254
+ return;
255
+ }
256
+ logger?.info?.('[callLogs migration] starting online migration (add column + rebuild primary key)');
257
+ // A server-side statement_timeout (common in RDS parameter groups) would abort
258
+ // the long CONCURRENTLY build and leave an INVALID index. Disable it for this
259
+ // session only; it is reset before the connection returns to the pool.
260
+ await client.query(`SET statement_timeout = 0`);
261
+ // 1. Add the column (metadata-only on PG 11+, safe to repeat).
262
+ await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ADD COLUMN IF NOT EXISTS "${HASHED_EXTENSION_ID_COLUMN}" varchar(255) NOT NULL DEFAULT ''`);
263
+ // 2. Primary-key columns must be NOT NULL. Only touch extensionNumber if needed.
264
+ if (await postgresColumnIsNullableOnConnection(client, 'extensionNumber')) {
265
+ await client.query(`UPDATE "${CALL_LOGS_TABLE}" SET "extensionNumber" = '' WHERE "extensionNumber" IS NULL`);
266
+ await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ALTER COLUMN "extensionNumber" SET NOT NULL`);
267
+ }
268
+ // 3. Build the unique index without blocking traffic (cannot run in a transaction).
269
+ // Reuse an already-valid index from a prior run so a failed *attach* (step 4)
270
+ // never forces us to redo the expensive build; only rebuild if it is missing
271
+ // or was left INVALID by an interrupted run.
272
+ const indexState = await postgresIdentityIndexState(client);
273
+ if (indexState === 'invalid') {
274
+ await client.query(`DROP INDEX CONCURRENTLY IF EXISTS "${IDENTITY_INDEX_NAME}"`);
275
+ }
276
+ if (indexState !== 'valid') {
277
+ await client.query(`CREATE UNIQUE INDEX CONCURRENTLY "${IDENTITY_INDEX_NAME}" ON "${CALL_LOGS_TABLE}" (${CALL_LOG_IDENTITY_PK_COLUMNS.map((c) => `"${c}"`).join(', ')})`);
278
+ }
279
+ // 4. Swap the primary key onto the prebuilt index (brief ACCESS EXCLUSIVE lock,
280
+ // no rebuild). Bound the lock wait so that if a long-running query holds the
281
+ // table we fail fast and retry next boot, instead of parking an exclusive-lock
282
+ // request at the head of the queue and stalling all traffic behind it.
283
+ await client.query(`SET lock_timeout = '5s'`);
284
+ const pkName = await postgresPrimaryKeyNameOnConnection(client);
285
+ if (pkName) {
286
+ await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" DROP CONSTRAINT "${pkName}", ADD PRIMARY KEY USING INDEX "${IDENTITY_INDEX_NAME}"`);
287
+ }
288
+ else {
289
+ await client.query(`ALTER TABLE "${CALL_LOGS_TABLE}" ADD PRIMARY KEY USING INDEX "${IDENTITY_INDEX_NAME}"`);
290
+ }
291
+ logger?.info?.('[callLogs migration] completed');
292
+ }
293
+ finally {
294
+ // Restore session defaults before the connection goes back to the pool, so no
295
+ // other query inherits statement_timeout=0 / the short lock_timeout.
296
+ try {
297
+ await client.query('RESET statement_timeout');
298
+ await client.query('RESET lock_timeout');
299
+ }
300
+ catch (e) {
301
+ const error = e;
302
+ logger?.warn?.('[callLogs migration] failed to reset session settings', { message: error?.message });
303
+ }
304
+ if (lockAcquired) {
305
+ try {
306
+ await client.query(`SELECT pg_advisory_unlock(${CALL_LOGS_MIGRATION_LOCK_KEY})`);
307
+ }
308
+ catch (e) {
309
+ const error = e;
310
+ logger?.warn?.('[callLogs migration] failed to release advisory lock', { message: error?.message });
311
+ }
312
+ }
313
+ connectionManager.releaseConnection(client);
314
+ }
315
+ }
316
+ /**
317
+ * Automatic entry point. Safe to call fire-and-forget off the startup critical path:
318
+ * it never throws (failures are logged and retried on the next boot) and, on Postgres,
319
+ * never blocks readiness or live traffic.
320
+ */
321
+ async function runCallLogsSchemaMigration(sequelize, logger) {
322
+ try {
323
+ const dialect = sequelize.getDialect();
324
+ if (dialect === 'postgres') {
325
+ await ensureCallLogsHashedExtensionIdSchemaPostgresOnline(sequelize, logger);
326
+ }
327
+ else {
328
+ // sqlite / local / tests: tables are tiny, the transactional rebuild is instant.
329
+ await ensureCallLogsHashedExtensionIdSchema(sequelize);
330
+ }
331
+ }
332
+ catch (e) {
333
+ const error = e;
334
+ logger?.error?.('[callLogs migration] failed; will retry on next start', {
335
+ message: error?.message,
336
+ stack: error?.stack,
337
+ });
338
+ }
339
+ }
@@ -0,0 +1,4 @@
1
+ import type { OAuthAppLike, OAuthInfo, OperationFailureResult, RefreshableOAuthUser } from '../types';
2
+ declare function getOAuthApp({ clientId, clientSecret, accessTokenUri, authorizationUri, redirectUri, scopes }: OAuthInfo): OAuthAppLike;
3
+ declare function checkAndRefreshAccessToken(oauthApp: OAuthAppLike, user: RefreshableOAuthUser, tokenLockTimeout?: number): Promise<RefreshableOAuthUser | OperationFailureResult | null>;
4
+ export { checkAndRefreshAccessToken, getOAuthApp };