corsair 0.1.23 → 0.1.25

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 (272) hide show
  1. package/dist/core.js +1807 -17
  2. package/dist/db/index.d.ts +6 -6
  3. package/dist/db/kysely/database.d.ts.map +1 -1
  4. package/dist/db/kysely/sqlite-date-plugin.d.ts +6 -0
  5. package/dist/db/kysely/sqlite-date-plugin.d.ts.map +1 -0
  6. package/dist/db.d.ts +1 -0
  7. package/dist/db.d.ts.map +1 -1
  8. package/dist/db.js +157 -16
  9. package/dist/index.js +24995 -17
  10. package/dist/orm.js +845 -28
  11. package/dist/plugins/github/index.js +2747 -291
  12. package/dist/plugins/index.js +21282 -21
  13. package/dist/plugins/linear/index.js +3532 -269
  14. package/dist/plugins/posthog/index.js +1072 -116
  15. package/dist/plugins/resend/index.js +1812 -197
  16. package/dist/plugins/slack/index.js +4251 -504
  17. package/dist/plugins/spotify/index.js +2550 -382
  18. package/dist/tsup.config.d.ts.map +1 -1
  19. package/package.json +1 -1
  20. package/dist/async-core/ApiError.js +0 -27
  21. package/dist/async-core/ApiRequestOptions.js +0 -1
  22. package/dist/async-core/ApiResult.js +0 -1
  23. package/dist/async-core/CancelablePromise.js +0 -95
  24. package/dist/async-core/OpenAPI.js +0 -1
  25. package/dist/async-core/rate-limit.js +0 -79
  26. package/dist/async-core/request.js +0 -313
  27. package/dist/async-core/webhook-handler.js +0 -40
  28. package/dist/async-core/webhook-utils.js +0 -57
  29. package/dist/core/auth/encryption.js +0 -177
  30. package/dist/core/auth/errors/index.js +0 -1
  31. package/dist/core/auth/errors/missing-config.js +0 -25
  32. package/dist/core/auth/index.js +0 -7
  33. package/dist/core/auth/key-manager.js +0 -386
  34. package/dist/core/auth/types.js +0 -28
  35. package/dist/core/client/index.js +0 -227
  36. package/dist/core/constants.js +0 -15
  37. package/dist/core/endpoints/bind.js +0 -154
  38. package/dist/core/endpoints/index.js +0 -1
  39. package/dist/core/errors/handler.js +0 -30
  40. package/dist/core/errors/index.js +0 -1
  41. package/dist/core/index.js +0 -64
  42. package/dist/core/inspect/index.js +0 -471
  43. package/dist/core/permissions/index.js +0 -178
  44. package/dist/core/plugins/index.js +0 -1
  45. package/dist/core/utils.js +0 -30
  46. package/dist/core/webhooks/bind.js +0 -77
  47. package/dist/core/webhooks/index.js +0 -1
  48. package/dist/db/index.js +0 -106
  49. package/dist/db/kysely/database.js +0 -33
  50. package/dist/db/kysely/orm.js +0 -334
  51. package/dist/db/kysely/postgres.js +0 -20
  52. package/dist/db/kysely/sqlite.js +0 -38
  53. package/dist/db/orm.js +0 -579
  54. package/dist/permissions/index.js +0 -146
  55. package/dist/plugins/discord/client.js +0 -42
  56. package/dist/plugins/discord/endpoints/channels.js +0 -19
  57. package/dist/plugins/discord/endpoints/guilds.js +0 -25
  58. package/dist/plugins/discord/endpoints/index.js +0 -8
  59. package/dist/plugins/discord/endpoints/members.js +0 -55
  60. package/dist/plugins/discord/endpoints/messages.js +0 -97
  61. package/dist/plugins/discord/endpoints/reactions.js +0 -23
  62. package/dist/plugins/discord/endpoints/threads.js +0 -37
  63. package/dist/plugins/discord/endpoints/types.js +0 -302
  64. package/dist/plugins/discord/error-handlers.js +0 -134
  65. package/dist/plugins/discord/index.js +0 -272
  66. package/dist/plugins/discord/schema/database.js +0 -51
  67. package/dist/plugins/discord/schema/index.js +0 -10
  68. package/dist/plugins/discord/webhooks/index.js +0 -8
  69. package/dist/plugins/discord/webhooks/interactions.js +0 -108
  70. package/dist/plugins/discord/webhooks/types.js +0 -205
  71. package/dist/plugins/github/api.test.js +0 -182
  72. package/dist/plugins/github/client.js +0 -46
  73. package/dist/plugins/github/endpoints/index.js +0 -37
  74. package/dist/plugins/github/endpoints/issues.js +0 -94
  75. package/dist/plugins/github/endpoints/pull-requests.js +0 -60
  76. package/dist/plugins/github/endpoints/releases.js +0 -66
  77. package/dist/plugins/github/endpoints/repositories.js +0 -73
  78. package/dist/plugins/github/endpoints/types.js +0 -651
  79. package/dist/plugins/github/endpoints/workflows.js +0 -44
  80. package/dist/plugins/github/integration.test.js +0 -520
  81. package/dist/plugins/github/schema/database.js +0 -134
  82. package/dist/plugins/github/schema/index.js +0 -16
  83. package/dist/plugins/github/types.js +0 -1
  84. package/dist/plugins/github/webhooks/index.js +0 -16
  85. package/dist/plugins/github/webhooks/pull-requests.js +0 -200
  86. package/dist/plugins/github/webhooks/push.js +0 -27
  87. package/dist/plugins/github/webhooks/stars.js +0 -59
  88. package/dist/plugins/github/webhooks/types.js +0 -247
  89. package/dist/plugins/gmail/api.test.js +0 -383
  90. package/dist/plugins/gmail/client.js +0 -66
  91. package/dist/plugins/gmail/endpoints/drafts.js +0 -103
  92. package/dist/plugins/gmail/endpoints/index.js +0 -38
  93. package/dist/plugins/gmail/endpoints/labels.js +0 -89
  94. package/dist/plugins/gmail/endpoints/messages.js +0 -213
  95. package/dist/plugins/gmail/endpoints/threads.js +0 -110
  96. package/dist/plugins/gmail/endpoints/types.js +0 -330
  97. package/dist/plugins/gmail/index.js +0 -326
  98. package/dist/plugins/gmail/integration.test.js +0 -110
  99. package/dist/plugins/gmail/schema/database.js +0 -59
  100. package/dist/plugins/gmail/schema/index.js +0 -17
  101. package/dist/plugins/gmail/types.js +0 -1
  102. package/dist/plugins/gmail/webhooks/index.js +0 -5
  103. package/dist/plugins/gmail/webhooks/messages.js +0 -441
  104. package/dist/plugins/gmail/webhooks/types.js +0 -68
  105. package/dist/plugins/googlecalendar/api.test.js +0 -134
  106. package/dist/plugins/googlecalendar/client.js +0 -66
  107. package/dist/plugins/googlecalendar/endpoints/calendar.js +0 -17
  108. package/dist/plugins/googlecalendar/endpoints/events.js +0 -123
  109. package/dist/plugins/googlecalendar/endpoints/index.js +0 -13
  110. package/dist/plugins/googlecalendar/endpoints/types.js +0 -268
  111. package/dist/plugins/googlecalendar/index.js +0 -153
  112. package/dist/plugins/googlecalendar/integration.test.js +0 -149
  113. package/dist/plugins/googlecalendar/schema/database.js +0 -82
  114. package/dist/plugins/googlecalendar/schema/index.js +0 -8
  115. package/dist/plugins/googlecalendar/setup.js +0 -280
  116. package/dist/plugins/googlecalendar/types.js +0 -1
  117. package/dist/plugins/googlecalendar/webhooks/events.js +0 -136
  118. package/dist/plugins/googlecalendar/webhooks/index.js +0 -5
  119. package/dist/plugins/googlecalendar/webhooks/types.js +0 -75
  120. package/dist/plugins/googledrive/api.test.js +0 -473
  121. package/dist/plugins/googledrive/client.js +0 -66
  122. package/dist/plugins/googledrive/endpoints/files.js +0 -210
  123. package/dist/plugins/googledrive/endpoints/folders.js +0 -130
  124. package/dist/plugins/googledrive/endpoints/index.js +0 -34
  125. package/dist/plugins/googledrive/endpoints/search.js +0 -49
  126. package/dist/plugins/googledrive/endpoints/sharedDrives.js +0 -124
  127. package/dist/plugins/googledrive/endpoints/types.js +0 -337
  128. package/dist/plugins/googledrive/index.js +0 -292
  129. package/dist/plugins/googledrive/integration.test.js +0 -139
  130. package/dist/plugins/googledrive/schema/database.js +0 -123
  131. package/dist/plugins/googledrive/schema/index.js +0 -9
  132. package/dist/plugins/googledrive/types.js +0 -1
  133. package/dist/plugins/googledrive/webhooks/changes.js +0 -272
  134. package/dist/plugins/googledrive/webhooks/index.js +0 -5
  135. package/dist/plugins/googledrive/webhooks/types.js +0 -82
  136. package/dist/plugins/googlesheets/api.test.js +0 -344
  137. package/dist/plugins/googlesheets/client.js +0 -66
  138. package/dist/plugins/googlesheets/endpoints/index.js +0 -17
  139. package/dist/plugins/googlesheets/endpoints/sheets.js +0 -325
  140. package/dist/plugins/googlesheets/endpoints/spreadsheets.js +0 -46
  141. package/dist/plugins/googlesheets/endpoints/types.js +0 -146
  142. package/dist/plugins/googlesheets/index.js +0 -184
  143. package/dist/plugins/googlesheets/integration.test.js +0 -106
  144. package/dist/plugins/googlesheets/schema/database.js +0 -24
  145. package/dist/plugins/googlesheets/schema/index.js +0 -9
  146. package/dist/plugins/googlesheets/types.js +0 -1
  147. package/dist/plugins/googlesheets/webhooks/index.js +0 -5
  148. package/dist/plugins/googlesheets/webhooks/rows.js +0 -43
  149. package/dist/plugins/googlesheets/webhooks/types.js +0 -35
  150. package/dist/plugins/hubspot/api.test.js +0 -305
  151. package/dist/plugins/hubspot/client.js +0 -45
  152. package/dist/plugins/hubspot/endpoints/companies.js +0 -141
  153. package/dist/plugins/hubspot/endpoints/contact-lists.js +0 -16
  154. package/dist/plugins/hubspot/endpoints/contacts.js +0 -100
  155. package/dist/plugins/hubspot/endpoints/deals.js +0 -115
  156. package/dist/plugins/hubspot/endpoints/engagements.js +0 -71
  157. package/dist/plugins/hubspot/endpoints/index.js +0 -53
  158. package/dist/plugins/hubspot/endpoints/tickets.js +0 -94
  159. package/dist/plugins/hubspot/endpoints/types.js +0 -444
  160. package/dist/plugins/hubspot/error-handlers.js +0 -55
  161. package/dist/plugins/hubspot/index.js +0 -448
  162. package/dist/plugins/hubspot/integration.test.js +0 -590
  163. package/dist/plugins/hubspot/schema/database.js +0 -48
  164. package/dist/plugins/hubspot/schema/index.js +0 -15
  165. package/dist/plugins/hubspot/types.js +0 -1
  166. package/dist/plugins/hubspot/webhooks/companies.js +0 -127
  167. package/dist/plugins/hubspot/webhooks/contacts.js +0 -127
  168. package/dist/plugins/hubspot/webhooks/deals.js +0 -127
  169. package/dist/plugins/hubspot/webhooks/index.js +0 -25
  170. package/dist/plugins/hubspot/webhooks/tickets.js +0 -127
  171. package/dist/plugins/hubspot/webhooks/types.js +0 -148
  172. package/dist/plugins/linear/api.test.js +0 -705
  173. package/dist/plugins/linear/client.js +0 -124
  174. package/dist/plugins/linear/endpoints/comments.js +0 -187
  175. package/dist/plugins/linear/endpoints/index.js +0 -34
  176. package/dist/plugins/linear/endpoints/issues.js +0 -362
  177. package/dist/plugins/linear/endpoints/projects.js +0 -210
  178. package/dist/plugins/linear/endpoints/teams.js +0 -81
  179. package/dist/plugins/linear/endpoints/types.js +0 -644
  180. package/dist/plugins/linear/endpoints/users.js +0 -81
  181. package/dist/plugins/linear/error-handlers.js +0 -140
  182. package/dist/plugins/linear/integration.test.js +0 -427
  183. package/dist/plugins/linear/schema/database.js +0 -93
  184. package/dist/plugins/linear/schema/index.js +0 -11
  185. package/dist/plugins/linear/webhooks/comments.js +0 -139
  186. package/dist/plugins/linear/webhooks/index.js +0 -19
  187. package/dist/plugins/linear/webhooks/issues.js +0 -167
  188. package/dist/plugins/linear/webhooks/projects.js +0 -148
  189. package/dist/plugins/linear/webhooks/types.js +0 -237
  190. package/dist/plugins/posthog/api.test.js +0 -91
  191. package/dist/plugins/posthog/client.js +0 -122
  192. package/dist/plugins/posthog/endpoints/events.js +0 -120
  193. package/dist/plugins/posthog/endpoints/index.js +0 -9
  194. package/dist/plugins/posthog/endpoints/types.js +0 -55
  195. package/dist/plugins/posthog/integration.test.js +0 -132
  196. package/dist/plugins/posthog/schema/database.js +0 -10
  197. package/dist/plugins/posthog/schema/index.js +0 -7
  198. package/dist/plugins/posthog/webhooks/events.js +0 -51
  199. package/dist/plugins/posthog/webhooks/index.js +0 -5
  200. package/dist/plugins/posthog/webhooks/types.js +0 -72
  201. package/dist/plugins/resend/api.test.js +0 -98
  202. package/dist/plugins/resend/client.js +0 -101
  203. package/dist/plugins/resend/endpoints/domains.js +0 -87
  204. package/dist/plugins/resend/endpoints/emails.js +0 -78
  205. package/dist/plugins/resend/endpoints/index.js +0 -15
  206. package/dist/plugins/resend/endpoints/types.js +0 -132
  207. package/dist/plugins/resend/error-handlers.js +0 -39
  208. package/dist/plugins/resend/integration.test.js +0 -241
  209. package/dist/plugins/resend/schema/database.js +0 -24
  210. package/dist/plugins/resend/schema/index.js +0 -8
  211. package/dist/plugins/resend/webhooks/domains.js +0 -94
  212. package/dist/plugins/resend/webhooks/emails.js +0 -269
  213. package/dist/plugins/resend/webhooks/index.js +0 -17
  214. package/dist/plugins/resend/webhooks/types.js +0 -226
  215. package/dist/plugins/slack/api.test.js +0 -501
  216. package/dist/plugins/slack/client.js +0 -125
  217. package/dist/plugins/slack/endpoints/channels.js +0 -326
  218. package/dist/plugins/slack/endpoints/files.js +0 -54
  219. package/dist/plugins/slack/endpoints/index.js +0 -64
  220. package/dist/plugins/slack/endpoints/messages.js +0 -136
  221. package/dist/plugins/slack/endpoints/reactions.js +0 -53
  222. package/dist/plugins/slack/endpoints/stars.js +0 -26
  223. package/dist/plugins/slack/endpoints/types.js +0 -785
  224. package/dist/plugins/slack/endpoints/user-groups.js +0 -96
  225. package/dist/plugins/slack/endpoints/users.js +0 -82
  226. package/dist/plugins/slack/error-handlers.js +0 -103
  227. package/dist/plugins/slack/integration.test.js +0 -180
  228. package/dist/plugins/slack/schema/database.js +0 -108
  229. package/dist/plugins/slack/schema/index.js +0 -11
  230. package/dist/plugins/slack/webhooks/challenge.js +0 -31
  231. package/dist/plugins/slack/webhooks/channels.js +0 -49
  232. package/dist/plugins/slack/webhooks/files.js +0 -139
  233. package/dist/plugins/slack/webhooks/index.js +0 -28
  234. package/dist/plugins/slack/webhooks/messages.js +0 -82
  235. package/dist/plugins/slack/webhooks/reactions.js +0 -28
  236. package/dist/plugins/slack/webhooks/types.js +0 -694
  237. package/dist/plugins/slack/webhooks/users.js +0 -94
  238. package/dist/plugins/spotify/api.test.js +0 -212
  239. package/dist/plugins/spotify/client.js +0 -112
  240. package/dist/plugins/spotify/endpoints/albums.js +0 -63
  241. package/dist/plugins/spotify/endpoints/artists.js +0 -53
  242. package/dist/plugins/spotify/endpoints/index.js +0 -52
  243. package/dist/plugins/spotify/endpoints/library.js +0 -11
  244. package/dist/plugins/spotify/endpoints/my-data.js +0 -19
  245. package/dist/plugins/spotify/endpoints/player.js +0 -124
  246. package/dist/plugins/spotify/endpoints/playlists.js +0 -90
  247. package/dist/plugins/spotify/endpoints/tracks.js +0 -37
  248. package/dist/plugins/spotify/endpoints/types.js +0 -572
  249. package/dist/plugins/spotify/error-handlers.js +0 -124
  250. package/dist/plugins/spotify/integration.test.js +0 -448
  251. package/dist/plugins/spotify/schema/database.js +0 -201
  252. package/dist/plugins/spotify/schema/index.js +0 -12
  253. package/dist/plugins/spotify/webhooks/example.js +0 -31
  254. package/dist/plugins/spotify/webhooks/index.js +0 -5
  255. package/dist/plugins/spotify/webhooks/types.js +0 -99
  256. package/dist/plugins/tavily/client.js +0 -47
  257. package/dist/plugins/tavily/endpoints/index.js +0 -5
  258. package/dist/plugins/tavily/endpoints/search.js +0 -11
  259. package/dist/plugins/tavily/endpoints/types.js +0 -98
  260. package/dist/plugins/tavily/error-handlers.js +0 -134
  261. package/dist/plugins/tavily/index.js +0 -166
  262. package/dist/plugins/tavily/schema/database.js +0 -10
  263. package/dist/plugins/tavily/schema/index.js +0 -14
  264. package/dist/plugins/utils/events.js +0 -57
  265. package/dist/templates/plugin/generate.js +0 -1023
  266. package/dist/tests/error-handlers.test.js +0 -454
  267. package/dist/tests/hooks.test.js +0 -357
  268. package/dist/tests/plugins-test-utils.js +0 -28
  269. package/dist/tests/setup-db.js +0 -59
  270. package/dist/tests/slack-rate-limit-integration.test.js +0 -526
  271. package/dist/tsup.config.js +0 -31
  272. package/dist/webhooks/index.js +0 -174
package/dist/core.js CHANGED
@@ -1,17 +1,1807 @@
1
- /**
2
- * Corsair Core - Core types and utilities for building Corsair integrations
3
- *
4
- * This module exports all core infrastructure types including:
5
- * - Main factory function (createCorsair)
6
- * - Client types (CorsairClient, CorsairTenantWrapper, etc.)
7
- * - Plugin system types
8
- * - Endpoint and webhook infrastructure
9
- * - Auth types and utilities
10
- * - Error handling
11
- *
12
- * @example
13
- * ```ts
14
- * import { createCorsair, type CorsairPlugin } from 'corsair/core';
15
- * ```
16
- */
17
- export * from './core/index';
1
+ // db/kysely/database.ts
2
+ import { Kysely, PostgresDialect, SqliteDialect } from "kysely";
3
+
4
+ // db/kysely/sqlite-date-plugin.ts
5
+ import {
6
+ OperationNodeTransformer
7
+ } from "kysely";
8
+ function serializeValue(v) {
9
+ if (v instanceof Date) return v.toISOString();
10
+ if (v !== null && typeof v === "object" && !Buffer.isBuffer(v))
11
+ return JSON.stringify(v);
12
+ return v;
13
+ }
14
+ var SqliteSerializingTransformer = class extends OperationNodeTransformer {
15
+ transformValue(node) {
16
+ const serialized = serializeValue(node.value);
17
+ return serialized === node.value ? node : { ...node, value: serialized };
18
+ }
19
+ transformPrimitiveValueList(node) {
20
+ const serialized = node.values.map(serializeValue);
21
+ const changed = serialized.some((v, i) => v !== node.values[i]);
22
+ return changed ? { ...node, values: serialized } : node;
23
+ }
24
+ };
25
+ var transformer = new SqliteSerializingTransformer();
26
+ var SqliteDatePlugin = class {
27
+ transformQuery(args) {
28
+ return transformer.transformNode(args.node);
29
+ }
30
+ async transformResult(args) {
31
+ return args.result;
32
+ }
33
+ };
34
+
35
+ // db/kysely/database.ts
36
+ function isPgPool(input) {
37
+ return typeof input.query === "function" && typeof input.connect === "function";
38
+ }
39
+ function isBetterSqlite3(input) {
40
+ const db = input;
41
+ return typeof db.prepare === "function" && typeof db.exec === "function" && typeof db.close === "function" && !("query" in input);
42
+ }
43
+ function isKysely(input) {
44
+ return typeof input.selectFrom === "function";
45
+ }
46
+ function createCorsairDatabase(input) {
47
+ if (isKysely(input)) {
48
+ return { db: input };
49
+ }
50
+ if (isBetterSqlite3(input)) {
51
+ const db = new Kysely({
52
+ dialect: new SqliteDialect({ database: input }),
53
+ plugins: [new SqliteDatePlugin()]
54
+ });
55
+ return { db };
56
+ }
57
+ if (isPgPool(input)) {
58
+ const db = new Kysely({
59
+ dialect: new PostgresDialect({ pool: input })
60
+ });
61
+ return { db };
62
+ }
63
+ throw new Error(
64
+ "Unsupported database input. Expected a pg Pool, better-sqlite3 Database, or a Kysely instance."
65
+ );
66
+ }
67
+
68
+ // core/auth/encryption.ts
69
+ import { createCipheriv, createDecipheriv, randomBytes, scrypt } from "crypto";
70
+ import { promisify } from "util";
71
+ var scryptAsync = promisify(scrypt);
72
+ var ALGORITHM = "aes-256-gcm";
73
+ var IV_LENGTH = 12;
74
+ var AUTH_TAG_LENGTH = 16;
75
+ var SALT_LENGTH = 16;
76
+ var KEY_LENGTH = 32;
77
+ function generateDEK() {
78
+ return randomBytes(KEY_LENGTH).toString("base64");
79
+ }
80
+ async function encryptDEK(dek, kek) {
81
+ const salt = randomBytes(SALT_LENGTH);
82
+ const derivedKey = await scryptAsync(kek, salt, KEY_LENGTH);
83
+ const iv = randomBytes(IV_LENGTH);
84
+ const cipher = createCipheriv(ALGORITHM, derivedKey, iv, {
85
+ authTagLength: AUTH_TAG_LENGTH
86
+ });
87
+ const encrypted = Buffer.concat([cipher.update(dek, "utf8"), cipher.final()]);
88
+ const authTag = cipher.getAuthTag();
89
+ return [
90
+ salt.toString("base64"),
91
+ iv.toString("base64"),
92
+ authTag.toString("base64"),
93
+ encrypted.toString("base64")
94
+ ].join(":");
95
+ }
96
+ async function decryptDEK(encryptedDek, kek) {
97
+ const [saltB64, ivB64, authTagB64, encryptedB64] = encryptedDek.split(":");
98
+ if (!saltB64 || !ivB64 || !authTagB64 || !encryptedB64) {
99
+ throw new Error("Invalid encrypted DEK format");
100
+ }
101
+ const salt = Buffer.from(saltB64, "base64");
102
+ const iv = Buffer.from(ivB64, "base64");
103
+ const authTag = Buffer.from(authTagB64, "base64");
104
+ const encrypted = Buffer.from(encryptedB64, "base64");
105
+ const derivedKey = await scryptAsync(kek, salt, KEY_LENGTH);
106
+ const decipher = createDecipheriv(ALGORITHM, derivedKey, iv, {
107
+ authTagLength: AUTH_TAG_LENGTH
108
+ });
109
+ decipher.setAuthTag(authTag);
110
+ const decrypted = Buffer.concat([
111
+ decipher.update(encrypted),
112
+ decipher.final()
113
+ ]);
114
+ return decrypted.toString("utf8");
115
+ }
116
+ function encryptWithDEK(data, dek) {
117
+ const key = Buffer.from(dek, "base64");
118
+ const iv = randomBytes(IV_LENGTH);
119
+ const cipher = createCipheriv(ALGORITHM, key, iv, {
120
+ authTagLength: AUTH_TAG_LENGTH
121
+ });
122
+ const encrypted = Buffer.concat([
123
+ cipher.update(data, "utf8"),
124
+ cipher.final()
125
+ ]);
126
+ const authTag = cipher.getAuthTag();
127
+ return [
128
+ iv.toString("base64"),
129
+ authTag.toString("base64"),
130
+ encrypted.toString("base64")
131
+ ].join(":");
132
+ }
133
+ function decryptWithDEK(encryptedData, dek) {
134
+ const [ivB64, authTagB64, encryptedB64] = encryptedData.split(":");
135
+ if (!ivB64 || !authTagB64 || !encryptedB64) {
136
+ throw new Error("Invalid encrypted data format");
137
+ }
138
+ const key = Buffer.from(dek, "base64");
139
+ const iv = Buffer.from(ivB64, "base64");
140
+ const authTag = Buffer.from(authTagB64, "base64");
141
+ const encrypted = Buffer.from(encryptedB64, "base64");
142
+ const decipher = createDecipheriv(ALGORITHM, key, iv, {
143
+ authTagLength: AUTH_TAG_LENGTH
144
+ });
145
+ decipher.setAuthTag(authTag);
146
+ const decrypted = Buffer.concat([
147
+ decipher.update(encrypted),
148
+ decipher.final()
149
+ ]);
150
+ return decrypted.toString("utf8");
151
+ }
152
+ function encryptConfig(config, dek) {
153
+ const encrypted = {};
154
+ for (const [key, value] of Object.entries(config)) {
155
+ encrypted[key] = encryptWithDEK(value, dek);
156
+ }
157
+ return encrypted;
158
+ }
159
+ function decryptConfig(encryptedConfig, dek) {
160
+ const decrypted = {};
161
+ for (const [key, value] of Object.entries(encryptedConfig)) {
162
+ decrypted[key] = decryptWithDEK(value, dek);
163
+ }
164
+ return decrypted;
165
+ }
166
+ function reEncryptConfig(encryptedConfig, oldDek, newDek) {
167
+ const decrypted = decryptConfig(encryptedConfig, oldDek);
168
+ return encryptConfig(decrypted, newDek);
169
+ }
170
+
171
+ // core/auth/errors/missing-config.ts
172
+ function createMissingConfigProxy(hasDatabase, hasKek) {
173
+ const missingConfig = [];
174
+ if (!hasDatabase) missingConfig.push("database");
175
+ if (!hasKek) missingConfig.push("kek");
176
+ const proxyTarget = {};
177
+ return new Proxy(proxyTarget, {
178
+ get(_target, prop) {
179
+ const isPlural = missingConfig.length > 1;
180
+ throw new Error(
181
+ `corsair.keys.${String(prop)}: Cannot access keys because ${missingConfig.join(" and ")} ${isPlural ? "are" : "is"} not configured. Provide both 'database' and 'kek' in createCorsair() to enable key management.
182
+
183
+ To generate a KEK, run: openssl rand -base64 ${KEY_LENGTH}`
184
+ );
185
+ }
186
+ });
187
+ }
188
+
189
+ // db/kysely/orm.ts
190
+ import { z } from "zod";
191
+
192
+ // core/utils.ts
193
+ import { v7 } from "uuid";
194
+ function generateUUID() {
195
+ return v7();
196
+ }
197
+
198
+ // db/kysely/postgres.ts
199
+ import { sql } from "kysely";
200
+ function escapeJsonPath(path) {
201
+ return path.replace(/'/g, "''");
202
+ }
203
+ function jsonbTextField(key) {
204
+ const escapedPath = escapeJsonPath(key);
205
+ return sql`data->>'${sql.raw(escapedPath)}'`;
206
+ }
207
+ function jsonbNumberField(key) {
208
+ const escapedPath = escapeJsonPath(key);
209
+ return sql`(data->>'${sql.raw(escapedPath)}')::numeric`;
210
+ }
211
+ function jsonbBooleanField(key) {
212
+ const escapedPath = escapeJsonPath(key);
213
+ return sql`(data->>'${sql.raw(escapedPath)}')::boolean`;
214
+ }
215
+ function jsonbTimestampField(key) {
216
+ const escapedPath = escapeJsonPath(key);
217
+ return sql`(data->>'${sql.raw(escapedPath)}')::timestamptz`;
218
+ }
219
+
220
+ // db/kysely/orm.ts
221
+ function parseJsonLike(value) {
222
+ if (typeof value === "string") {
223
+ try {
224
+ return JSON.parse(value);
225
+ } catch {
226
+ return value;
227
+ }
228
+ }
229
+ return value;
230
+ }
231
+ function unwrapSchema(schema) {
232
+ let current = schema;
233
+ while (current) {
234
+ if (current instanceof z.ZodOptional || current instanceof z.ZodNullable) {
235
+ current = current._def.innerType;
236
+ continue;
237
+ }
238
+ if (current instanceof z.ZodDefault) {
239
+ current = current._def.innerType;
240
+ continue;
241
+ }
242
+ if (current instanceof z.ZodEffects) {
243
+ current = current._def.schema;
244
+ continue;
245
+ }
246
+ break;
247
+ }
248
+ return current;
249
+ }
250
+ function getFieldType(schema) {
251
+ const unwrapped = unwrapSchema(schema);
252
+ if (unwrapped instanceof z.ZodString) return "string";
253
+ if (unwrapped instanceof z.ZodNumber) return "number";
254
+ if (unwrapped instanceof z.ZodBoolean) return "boolean";
255
+ if (unwrapped instanceof z.ZodDate) return "date";
256
+ return void 0;
257
+ }
258
+ function getDataFieldTypes(schema) {
259
+ const unwrapped = unwrapSchema(schema);
260
+ if (!(unwrapped instanceof z.ZodObject)) return {};
261
+ const shape = unwrapped.shape;
262
+ const fieldTypes = {};
263
+ for (const [key, fieldSchema] of Object.entries(shape)) {
264
+ const fieldType = getFieldType(fieldSchema);
265
+ if (fieldType) fieldTypes[key] = fieldType;
266
+ }
267
+ return fieldTypes;
268
+ }
269
+ function applyStringFilter(q, expr, filterValue) {
270
+ if (typeof filterValue === "string") {
271
+ return q.where(expr, "=", filterValue);
272
+ }
273
+ if (typeof filterValue === "object" && filterValue !== null && !Array.isArray(filterValue)) {
274
+ const obj = filterValue;
275
+ if ("equals" in obj && typeof obj.equals === "string") {
276
+ q = q.where(expr, "=", obj.equals);
277
+ }
278
+ if ("contains" in obj && typeof obj.contains === "string") {
279
+ q = q.where(expr, "like", `%${obj.contains}%`);
280
+ }
281
+ if ("startsWith" in obj && typeof obj.startsWith === "string") {
282
+ q = q.where(expr, "like", `${obj.startsWith}%`);
283
+ }
284
+ if ("endsWith" in obj && typeof obj.endsWith === "string") {
285
+ q = q.where(expr, "like", `%${obj.endsWith}`);
286
+ }
287
+ if ("in" in obj && Array.isArray(obj.in)) {
288
+ q = q.where(expr, "in", obj.in);
289
+ }
290
+ }
291
+ return q;
292
+ }
293
+ function applyNumberFilter(q, expr, filterValue) {
294
+ if (typeof filterValue === "number") {
295
+ return q.where(expr, "=", filterValue);
296
+ }
297
+ if (typeof filterValue === "object" && filterValue !== null && !Array.isArray(filterValue)) {
298
+ const obj = filterValue;
299
+ if (typeof obj.equals === "number") q = q.where(expr, "=", obj.equals);
300
+ if (typeof obj.gt === "number") q = q.where(expr, ">", obj.gt);
301
+ if (typeof obj.gte === "number") q = q.where(expr, ">=", obj.gte);
302
+ if (typeof obj.lt === "number") q = q.where(expr, "<", obj.lt);
303
+ if (typeof obj.lte === "number") q = q.where(expr, "<=", obj.lte);
304
+ if (Array.isArray(obj.in)) q = q.where(expr, "in", obj.in);
305
+ }
306
+ return q;
307
+ }
308
+ function applyBooleanFilter(q, expr, filterValue) {
309
+ if (typeof filterValue === "boolean") {
310
+ return q.where(expr, "=", filterValue);
311
+ }
312
+ if (typeof filterValue === "object" && filterValue !== null && !Array.isArray(filterValue)) {
313
+ const obj = filterValue;
314
+ if (typeof obj.equals === "boolean") q = q.where(expr, "=", obj.equals);
315
+ }
316
+ return q;
317
+ }
318
+ function applyDateFilter(q, expr, filterValue) {
319
+ if (filterValue instanceof Date) {
320
+ return q.where(expr, "=", filterValue);
321
+ }
322
+ if (typeof filterValue === "object" && filterValue !== null && !Array.isArray(filterValue)) {
323
+ const obj = filterValue;
324
+ if (obj.equals instanceof Date) q = q.where(expr, "=", obj.equals);
325
+ if (obj.before instanceof Date) q = q.where(expr, "<", obj.before);
326
+ if (obj.after instanceof Date) q = q.where(expr, ">", obj.after);
327
+ if (Array.isArray(obj.between) && obj.between.length === 2) {
328
+ const [start, end] = obj.between;
329
+ if (start instanceof Date) q = q.where(expr, ">=", start);
330
+ if (end instanceof Date) q = q.where(expr, "<=", end);
331
+ }
332
+ }
333
+ return q;
334
+ }
335
+ function applyDataFilter(q, key, fieldType, filterValue) {
336
+ if (fieldType === "number") {
337
+ return applyNumberFilter(q, jsonbNumberField(key), filterValue);
338
+ }
339
+ if (fieldType === "boolean") {
340
+ return applyBooleanFilter(q, jsonbBooleanField(key), filterValue);
341
+ }
342
+ if (fieldType === "date") {
343
+ return applyDateFilter(q, jsonbTimestampField(key), filterValue);
344
+ }
345
+ return applyStringFilter(q, jsonbTextField(key), filterValue);
346
+ }
347
+ function applyEntityFieldFilter(q, key, filterValue) {
348
+ if (typeof filterValue === "object" && filterValue !== null && !Array.isArray(filterValue)) {
349
+ const obj = filterValue;
350
+ if ("equals" in obj) q = q.where(key, "=", obj.equals);
351
+ if ("contains" in obj && typeof obj.contains === "string") {
352
+ q = q.where(key, "like", `%${obj.contains}%`);
353
+ }
354
+ if ("startsWith" in obj && typeof obj.startsWith === "string") {
355
+ q = q.where(key, "like", `${obj.startsWith}%`);
356
+ }
357
+ if ("endsWith" in obj && typeof obj.endsWith === "string") {
358
+ q = q.where(key, "like", `%${obj.endsWith}`);
359
+ }
360
+ if ("in" in obj && Array.isArray(obj.in)) {
361
+ q = q.where(key, "in", obj.in);
362
+ }
363
+ return q;
364
+ }
365
+ return q.where(key, "=", filterValue);
366
+ }
367
+ function parseCountValue(countVal) {
368
+ if (typeof countVal === "number") return countVal;
369
+ if (typeof countVal === "bigint") return Number(countVal);
370
+ return Number.parseInt(String(countVal ?? 0), 10);
371
+ }
372
+ function baseQuery(db, accountId, entityTypeName) {
373
+ return db.selectFrom("corsair_entities").selectAll().where("account_id", "=", accountId).where("entity_type", "=", entityTypeName);
374
+ }
375
+ function createKyselyEntityClient(db, getAccountId, entityTypeName, version, dataSchema) {
376
+ const dataFieldTypes = getDataFieldTypes(dataSchema);
377
+ function parseRow(row) {
378
+ const data = parseJsonLike(row.data);
379
+ return {
380
+ ...row,
381
+ data: dataSchema.parse(data)
382
+ };
383
+ }
384
+ return {
385
+ findByEntityId: async (entityId) => {
386
+ const accountId = await getAccountId();
387
+ const row = await baseQuery(db, accountId, entityTypeName).where("entity_id", "=", entityId).executeTakeFirst();
388
+ return row ? parseRow(row) : null;
389
+ },
390
+ findById: async (id) => {
391
+ const accountId = await getAccountId();
392
+ const row = await baseQuery(db, accountId, entityTypeName).where("id", "=", id).executeTakeFirst();
393
+ return row ? parseRow(row) : null;
394
+ },
395
+ findManyByEntityIds: async (entityIds) => {
396
+ if (entityIds.length === 0) return [];
397
+ const accountId = await getAccountId();
398
+ const rows = await baseQuery(db, accountId, entityTypeName).where("entity_id", "in", entityIds).execute();
399
+ return rows.map(parseRow);
400
+ },
401
+ list: async (options) => {
402
+ const accountId = await getAccountId();
403
+ let q = baseQuery(db, accountId, entityTypeName);
404
+ if (typeof options?.limit === "number") q = q.limit(options.limit);
405
+ if (typeof options?.offset === "number") q = q.offset(options.offset);
406
+ const rows = await q.execute();
407
+ return rows.map(parseRow);
408
+ },
409
+ search: async (options) => {
410
+ const accountId = await getAccountId();
411
+ let q = baseQuery(db, accountId, entityTypeName);
412
+ const reservedKeys = /* @__PURE__ */ new Set(["data", "limit", "offset"]);
413
+ for (const [key, filterValue] of Object.entries(options)) {
414
+ if (reservedKeys.has(key) || filterValue === void 0) continue;
415
+ q = applyEntityFieldFilter(q, key, filterValue);
416
+ }
417
+ if (options.data && typeof options.data === "object") {
418
+ for (const [key, filterValue] of Object.entries(options.data)) {
419
+ if (filterValue === void 0) continue;
420
+ const fieldType = dataFieldTypes[key] ?? "string";
421
+ q = applyDataFilter(q, key, fieldType, filterValue);
422
+ }
423
+ }
424
+ if (typeof options.limit === "number") q = q.limit(options.limit);
425
+ if (typeof options.offset === "number") q = q.offset(options.offset);
426
+ const rows = await q.execute();
427
+ return rows.map(parseRow);
428
+ },
429
+ upsertByEntityId: async (entityId, data) => {
430
+ const accountId = await getAccountId();
431
+ const parsed = dataSchema.parse(data);
432
+ const now = /* @__PURE__ */ new Date();
433
+ const existing = await baseQuery(db, accountId, entityTypeName).select("id").where("entity_id", "=", entityId).executeTakeFirst();
434
+ if (existing?.id) {
435
+ await db.updateTable("corsair_entities").set({ version, data: parsed, updated_at: now }).where("id", "=", existing.id).execute();
436
+ const updated = await db.selectFrom("corsair_entities").selectAll().where("id", "=", existing.id).executeTakeFirst();
437
+ return parseRow(updated);
438
+ }
439
+ const id = generateUUID();
440
+ await db.insertInto("corsair_entities").values({
441
+ id,
442
+ created_at: now,
443
+ updated_at: now,
444
+ account_id: accountId,
445
+ entity_id: entityId,
446
+ entity_type: entityTypeName,
447
+ version,
448
+ data: parsed
449
+ }).execute();
450
+ const inserted = await db.selectFrom("corsair_entities").selectAll().where("id", "=", id).executeTakeFirst();
451
+ return parseRow(inserted);
452
+ },
453
+ deleteById: async (id) => {
454
+ const accountId = await getAccountId();
455
+ const res = await db.deleteFrom("corsair_entities").where("account_id", "=", accountId).where("entity_type", "=", entityTypeName).where("id", "=", id).executeTakeFirst();
456
+ return Number(res.numDeletedRows) > 0;
457
+ },
458
+ deleteByEntityId: async (entityId) => {
459
+ const accountId = await getAccountId();
460
+ const res = await db.deleteFrom("corsair_entities").where("account_id", "=", accountId).where("entity_type", "=", entityTypeName).where("entity_id", "=", entityId).executeTakeFirst();
461
+ return Number(res.numDeletedRows) > 0;
462
+ },
463
+ count: async () => {
464
+ const accountId = await getAccountId();
465
+ const row = await db.selectFrom("corsair_entities").select((eb) => eb.fn.countAll().as("count")).where("account_id", "=", accountId).where("entity_type", "=", entityTypeName).executeTakeFirst();
466
+ return parseCountValue(row?.count);
467
+ }
468
+ };
469
+ }
470
+
471
+ // core/auth/types.ts
472
+ var BASE_AUTH_FIELDS = {
473
+ oauth_2: {
474
+ integration: ["client_id", "client_secret", "redirect_url"],
475
+ account: [
476
+ "access_token",
477
+ "refresh_token",
478
+ "expires_at",
479
+ "scope",
480
+ "webhook_signature"
481
+ ]
482
+ },
483
+ api_key: {
484
+ integration: [],
485
+ account: ["api_key", "webhook_signature"]
486
+ },
487
+ bot_token: {
488
+ integration: [],
489
+ account: ["bot_token", "webhook_signature"]
490
+ }
491
+ };
492
+
493
+ // core/auth/key-manager.ts
494
+ function createFieldAccessors(getDecryptedConfig, updateConfig, fields) {
495
+ const accessors = {};
496
+ for (const field of fields) {
497
+ accessors[`get_${field}`] = async () => {
498
+ const config = await getDecryptedConfig();
499
+ return config[field] ?? null;
500
+ };
501
+ accessors[`set_${field}`] = async (value) => {
502
+ await updateConfig({ [field]: value });
503
+ };
504
+ }
505
+ return accessors;
506
+ }
507
+ var parseConfig = (config) => {
508
+ if (!config) return {};
509
+ if (typeof config === "string") {
510
+ try {
511
+ return JSON.parse(config);
512
+ } catch {
513
+ return {};
514
+ }
515
+ }
516
+ return config;
517
+ };
518
+ function createIntegrationKeyManager(options) {
519
+ const {
520
+ authType,
521
+ integrationName,
522
+ kek,
523
+ database,
524
+ extraIntegrationFields = []
525
+ } = options;
526
+ const allFields = [
527
+ ...BASE_AUTH_FIELDS[authType].integration,
528
+ ...extraIntegrationFields
529
+ ];
530
+ let cachedIntegration = null;
531
+ const ctx = {
532
+ kek,
533
+ integrationName,
534
+ getIntegration: async () => {
535
+ if (cachedIntegration) return cachedIntegration;
536
+ const integration = await database.db.selectFrom("corsair_integrations").selectAll().where("name", "=", integrationName).executeTakeFirst();
537
+ if (!integration) {
538
+ throw new Error(
539
+ `Integration "${integrationName}" not found. Make sure to create the integration first.`
540
+ );
541
+ }
542
+ cachedIntegration = {
543
+ id: integration.id,
544
+ config: parseConfig(integration.config),
545
+ dek: integration.dek ?? null
546
+ };
547
+ return cachedIntegration;
548
+ },
549
+ updateIntegration: async (data) => {
550
+ const integration = await ctx.getIntegration();
551
+ await database.db.updateTable("corsair_integrations").set({
552
+ ...data.config !== void 0 ? { config: data.config } : {},
553
+ ...data.dek !== void 0 ? { dek: data.dek } : {},
554
+ updated_at: /* @__PURE__ */ new Date()
555
+ }).where("id", "=", integration.id).execute();
556
+ cachedIntegration = null;
557
+ }
558
+ };
559
+ let cachedDek = null;
560
+ const getDecryptedDek = async () => {
561
+ if (cachedDek) return cachedDek;
562
+ const integration = await ctx.getIntegration();
563
+ if (!integration.dek) {
564
+ throw new Error(
565
+ `No DEK found for integration "${integrationName}". Initialize the integration first.`
566
+ );
567
+ }
568
+ cachedDek = await decryptDEK(integration.dek, kek);
569
+ return cachedDek;
570
+ };
571
+ const getDecryptedConfig = async () => {
572
+ const integration = await ctx.getIntegration();
573
+ const dek = await getDecryptedDek();
574
+ const config = integration.config;
575
+ if (!config || Object.keys(config).length === 0) {
576
+ return {};
577
+ }
578
+ return decryptConfig(config, dek);
579
+ };
580
+ const updateConfig = async (updates) => {
581
+ const dek = await getDecryptedDek();
582
+ const currentConfig = await getDecryptedConfig();
583
+ const newConfig = { ...currentConfig };
584
+ for (const [key, value] of Object.entries(updates)) {
585
+ if (value === null) {
586
+ delete newConfig[key];
587
+ } else {
588
+ newConfig[key] = value;
589
+ }
590
+ }
591
+ const encryptedConfig = encryptConfig(newConfig, dek);
592
+ await ctx.updateIntegration({ config: encryptedConfig });
593
+ };
594
+ const manager = {
595
+ get_dek: getDecryptedDek,
596
+ issue_new_dek: async () => {
597
+ const integration = await ctx.getIntegration();
598
+ const newDek = generateDEK();
599
+ let newConfig = {};
600
+ if (integration.dek) {
601
+ const oldDek = await decryptDEK(integration.dek, kek);
602
+ const config = integration.config;
603
+ if (config && Object.keys(config).length > 0) {
604
+ newConfig = reEncryptConfig(config, oldDek, newDek);
605
+ }
606
+ }
607
+ const encryptedNewDek = await encryptDEK(newDek, kek);
608
+ await ctx.updateIntegration({
609
+ config: newConfig,
610
+ dek: encryptedNewDek
611
+ });
612
+ cachedDek = newDek;
613
+ return newDek;
614
+ },
615
+ // Auto-generated field accessors
616
+ ...createFieldAccessors(getDecryptedConfig, updateConfig, allFields)
617
+ };
618
+ return manager;
619
+ }
620
+ function createAccountKeyManager(options) {
621
+ const {
622
+ authType,
623
+ integrationName,
624
+ tenantId,
625
+ kek,
626
+ database,
627
+ extraAccountFields = []
628
+ } = options;
629
+ const allFields = [
630
+ ...BASE_AUTH_FIELDS[authType].account,
631
+ ...extraAccountFields
632
+ ];
633
+ let cachedAccount = null;
634
+ let cachedIntegration = null;
635
+ const getIntegration = async () => {
636
+ if (cachedIntegration) return cachedIntegration;
637
+ const integration = await database.db.selectFrom("corsair_integrations").selectAll().where("name", "=", integrationName).executeTakeFirst();
638
+ if (!integration) {
639
+ throw new Error(
640
+ `Integration "${integrationName}" not found. Make sure to create the integration first.`
641
+ );
642
+ }
643
+ cachedIntegration = {
644
+ id: integration.id,
645
+ config: parseConfig(integration.config),
646
+ dek: integration.dek ?? null
647
+ };
648
+ return cachedIntegration;
649
+ };
650
+ const ctx = {
651
+ kek,
652
+ integrationName,
653
+ tenantId,
654
+ getIntegration,
655
+ getAccount: async () => {
656
+ if (cachedAccount) return cachedAccount;
657
+ const integration = await getIntegration();
658
+ const account = await database.db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integration.id).executeTakeFirst();
659
+ if (!account) {
660
+ throw new Error(
661
+ `Account not found for tenant "${tenantId}" and integration "${integrationName}". Make sure to create the account first.`
662
+ );
663
+ }
664
+ cachedAccount = {
665
+ id: account.id,
666
+ config: parseConfig(account.config),
667
+ dek: account.dek ?? null
668
+ };
669
+ return cachedAccount;
670
+ },
671
+ updateAccount: async (data) => {
672
+ const account = await ctx.getAccount();
673
+ await database.db.updateTable("corsair_accounts").set({
674
+ ...data.config !== void 0 ? { config: data.config } : {},
675
+ ...data.dek !== void 0 ? { dek: data.dek } : {},
676
+ updated_at: /* @__PURE__ */ new Date()
677
+ }).where("id", "=", account.id).execute();
678
+ cachedAccount = null;
679
+ }
680
+ };
681
+ let cachedDek = null;
682
+ let cachedIntegrationDek = null;
683
+ const getDecryptedDek = async () => {
684
+ if (cachedDek) return cachedDek;
685
+ const account = await ctx.getAccount();
686
+ if (!account.dek) {
687
+ throw new Error(
688
+ `No DEK found for account (tenant: "${tenantId}", integration: "${integrationName}"). Initialize the account first.`
689
+ );
690
+ }
691
+ cachedDek = await decryptDEK(account.dek, kek);
692
+ return cachedDek;
693
+ };
694
+ const getDecryptedIntegrationDek = async () => {
695
+ if (cachedIntegrationDek) return cachedIntegrationDek;
696
+ const integration = await ctx.getIntegration();
697
+ if (!integration.dek) {
698
+ throw new Error(
699
+ `No DEK found for integration "${integrationName}". Initialize the integration first.`
700
+ );
701
+ }
702
+ cachedIntegrationDek = await decryptDEK(integration.dek, kek);
703
+ return cachedIntegrationDek;
704
+ };
705
+ const getDecryptedConfig = async () => {
706
+ const account = await ctx.getAccount();
707
+ const dek = await getDecryptedDek();
708
+ const config = account.config;
709
+ if (!config || Object.keys(config).length === 0) {
710
+ return {};
711
+ }
712
+ return decryptConfig(config, dek);
713
+ };
714
+ const getDecryptedIntegrationConfig = async () => {
715
+ const integration = await ctx.getIntegration();
716
+ const dek = await getDecryptedIntegrationDek();
717
+ const config = integration.config;
718
+ if (!config || Object.keys(config).length === 0) {
719
+ return {};
720
+ }
721
+ return decryptConfig(config, dek);
722
+ };
723
+ const updateConfig = async (updates) => {
724
+ const dek = await getDecryptedDek();
725
+ const currentConfig = await getDecryptedConfig();
726
+ const newConfig = { ...currentConfig };
727
+ for (const [key, value] of Object.entries(updates)) {
728
+ if (value === null) {
729
+ delete newConfig[key];
730
+ } else {
731
+ newConfig[key] = value;
732
+ }
733
+ }
734
+ const encryptedConfig = encryptConfig(newConfig, dek);
735
+ await ctx.updateAccount({ config: encryptedConfig });
736
+ };
737
+ const manager = {
738
+ get_dek: getDecryptedDek,
739
+ issue_new_dek: async () => {
740
+ const account = await ctx.getAccount();
741
+ const newDek = generateDEK();
742
+ let newConfig = {};
743
+ if (account.dek) {
744
+ const oldDek = await decryptDEK(account.dek, kek);
745
+ const config = account.config;
746
+ if (config && Object.keys(config).length > 0) {
747
+ newConfig = reEncryptConfig(config, oldDek, newDek);
748
+ }
749
+ }
750
+ const encryptedNewDek = await encryptDEK(newDek, kek);
751
+ await ctx.updateAccount({
752
+ config: newConfig,
753
+ dek: encryptedNewDek
754
+ });
755
+ cachedDek = newDek;
756
+ return newDek;
757
+ },
758
+ // Auto-generated field accessors
759
+ ...createFieldAccessors(getDecryptedConfig, updateConfig, allFields)
760
+ };
761
+ if (authType === "oauth_2") {
762
+ manager.get_integration_credentials = async () => {
763
+ const config = await getDecryptedIntegrationConfig();
764
+ return {
765
+ client_id: config.client_id || null,
766
+ client_secret: config.client_secret || null,
767
+ redirect_url: config.redirect_url ?? null
768
+ };
769
+ };
770
+ }
771
+ return manager;
772
+ }
773
+ async function initializeIntegrationDEK(database, integrationName, kek) {
774
+ const integration = await database.db.selectFrom("corsair_integrations").selectAll().where("name", "=", integrationName).executeTakeFirst();
775
+ if (!integration) {
776
+ throw new Error(`Integration "${integrationName}" not found.`);
777
+ }
778
+ const dek = generateDEK();
779
+ const encryptedDek = await encryptDEK(dek, kek);
780
+ await database.db.updateTable("corsair_integrations").set({
781
+ dek: encryptedDek,
782
+ updated_at: /* @__PURE__ */ new Date()
783
+ }).where("id", "=", integration.id).execute();
784
+ return dek;
785
+ }
786
+ async function initializeAccountDEK(database, integrationName, tenantId, kek) {
787
+ const integration = await database.db.selectFrom("corsair_integrations").selectAll().where("name", "=", integrationName).executeTakeFirst();
788
+ if (!integration) {
789
+ throw new Error(`Integration "${integrationName}" not found.`);
790
+ }
791
+ const account = await database.db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integration.id).executeTakeFirst();
792
+ if (!account) {
793
+ throw new Error(
794
+ `Account not found for tenant "${tenantId}" and integration "${integrationName}".`
795
+ );
796
+ }
797
+ const dek = generateDEK();
798
+ const encryptedDek = await encryptDEK(dek, kek);
799
+ await database.db.updateTable("corsair_accounts").set({
800
+ dek: encryptedDek,
801
+ updated_at: /* @__PURE__ */ new Date()
802
+ }).where("id", "=", account.id).execute();
803
+ return dek;
804
+ }
805
+
806
+ // core/errors/handler.ts
807
+ var defaultErrorHandler = async (error, context) => {
808
+ console.error(`[corsair:${context.pluginId}:${context.operation}]`, {
809
+ error: error.message,
810
+ input: context.input
811
+ });
812
+ return {
813
+ maxRetries: 0
814
+ };
815
+ };
816
+ async function handleCorsairError(error, pluginId, operation, input, errorHandlers) {
817
+ const context = {
818
+ pluginId,
819
+ operation,
820
+ input,
821
+ originalError: error
822
+ };
823
+ const matchingHandlerName = Object.keys(errorHandlers).find(
824
+ (errorName) => errorHandlers[errorName]?.match(error, context)
825
+ );
826
+ const pluginSpecificErrorHandler = errorHandlers[matchingHandlerName || "DEFAULT"]?.handler;
827
+ const handler = pluginSpecificErrorHandler || defaultErrorHandler;
828
+ return await handler(error, context);
829
+ }
830
+
831
+ // core/permissions/index.ts
832
+ import { randomBytes as randomBytes2 } from "crypto";
833
+ import { v4 as uuidv4 } from "uuid";
834
+ var PERMISSION_MATRIX = {
835
+ open: { read: "allow", write: "allow", destructive: "allow" },
836
+ cautious: { read: "allow", write: "allow", destructive: "require_approval" },
837
+ strict: { read: "allow", write: "require_approval", destructive: "deny" },
838
+ readonly: { read: "allow", write: "deny", destructive: "deny" }
839
+ };
840
+ function evaluatePermission(riskLevel, mode, override) {
841
+ if (override !== void 0) return override;
842
+ return PERMISSION_MATRIX[mode][riskLevel];
843
+ }
844
+ function parseDurationMs(duration) {
845
+ const regex = /(\d+)(d|h|m|s)/g;
846
+ let total = 0;
847
+ let match;
848
+ while ((match = regex.exec(duration)) !== null) {
849
+ const value = parseInt(match[1], 10);
850
+ switch (match[2]) {
851
+ case "d":
852
+ total += value * 864e5;
853
+ break;
854
+ case "h":
855
+ total += value * 36e5;
856
+ break;
857
+ case "m":
858
+ total += value * 6e4;
859
+ break;
860
+ case "s":
861
+ total += value * 1e3;
862
+ break;
863
+ }
864
+ }
865
+ return total > 0 ? total : 10 * 60 * 1e3;
866
+ }
867
+ function buildPermissionsNamespace(db) {
868
+ return {
869
+ async find_by_permission_id(id) {
870
+ if (!db) return void 0;
871
+ return db.db.selectFrom("corsair_permissions").selectAll().where("id", "=", id).executeTakeFirst();
872
+ },
873
+ async find_by_token(token) {
874
+ if (!db) return void 0;
875
+ return db.db.selectFrom("corsair_permissions").selectAll().where("token", "=", token).executeTakeFirst();
876
+ },
877
+ async set_executing(id) {
878
+ if (!db) return;
879
+ await db.db.updateTable("corsair_permissions").set({ status: "executing", updated_at: /* @__PURE__ */ new Date() }).where("id", "=", id).execute();
880
+ },
881
+ async set_completed(id) {
882
+ if (!db) return;
883
+ await db.db.updateTable("corsair_permissions").set({ status: "completed", updated_at: /* @__PURE__ */ new Date() }).where("id", "=", id).execute();
884
+ }
885
+ };
886
+ }
887
+ async function enforcePermission(opts) {
888
+ const policy = evaluatePermission(opts.riskLevel, opts.mode, opts.override);
889
+ if (policy === "allow") return { result: "allow" };
890
+ const irreversibleNote = opts.meta?.irreversible ? " (irreversible)" : "";
891
+ const description = opts.meta?.description ? `${opts.meta.description}${irreversibleNote}` : `${opts.pluginId}.${opts.endpointPath}${irreversibleNote}`;
892
+ if (policy === "deny" || !opts.db) {
893
+ console.log(
894
+ `[corsair/${opts.pluginId}] '${opts.endpointPath}' blocked \u2014 denied by permission mode '${opts.mode}'.`,
895
+ `
896
+ Action: ${description}`,
897
+ `
898
+ To allow this, update the permission mode or add an override in your corsair config.`
899
+ );
900
+ return { result: "blocked" };
901
+ }
902
+ const argsJson = JSON.stringify(opts.args);
903
+ const now = (/* @__PURE__ */ new Date()).toISOString();
904
+ const tenantId = opts.tenantId ?? "default";
905
+ const existing = await opts.db.db.selectFrom("corsair_permissions").selectAll().where("plugin", "=", opts.pluginId).where("endpoint", "=", opts.endpointPath).where("args", "=", argsJson).where("tenant_id", "=", tenantId).where("expires_at", ">", now).where("status", "in", ["pending", "approved", "executing"]).orderBy("created_at", "desc").limit(1).executeTakeFirst();
906
+ if (existing) {
907
+ if (existing.status === "approved") {
908
+ const db = opts.db;
909
+ const permissionId = existing.id;
910
+ return {
911
+ result: "allow",
912
+ onComplete: async () => {
913
+ await db.db.updateTable("corsair_permissions").set({ status: "completed", updated_at: /* @__PURE__ */ new Date() }).where("id", "=", permissionId).execute();
914
+ }
915
+ };
916
+ }
917
+ if (existing.status === "executing") {
918
+ return { result: "allow" };
919
+ }
920
+ console.log(
921
+ `[corsair/${opts.pluginId}] '${opts.endpointPath}' blocked \u2014 approval already pending.`,
922
+ `
923
+ Action: ${description}`,
924
+ `
925
+ Permission ID: ${existing.id}`,
926
+ `
927
+ Use the token to approve or deny this request.`
928
+ );
929
+ return { result: "blocked" };
930
+ }
931
+ const id = uuidv4();
932
+ const token = randomBytes2(32).toString("hex");
933
+ const timeoutMs = opts.timeoutMs ?? 10 * 60 * 1e3;
934
+ const expiresAt = new Date(Date.now() + timeoutMs).toISOString();
935
+ await opts.db.db.insertInto("corsair_permissions").values({
936
+ id,
937
+ created_at: /* @__PURE__ */ new Date(),
938
+ updated_at: /* @__PURE__ */ new Date(),
939
+ token,
940
+ plugin: opts.pluginId,
941
+ endpoint: opts.endpointPath,
942
+ args: argsJson,
943
+ tenant_id: tenantId,
944
+ status: "pending",
945
+ expires_at: expiresAt
946
+ }).execute();
947
+ console.log(
948
+ `[corsair/${opts.pluginId}] '${opts.endpointPath}' blocked \u2014 approval required.`,
949
+ `
950
+ Action: ${description}`,
951
+ `
952
+ Permission ID: ${id}`,
953
+ `
954
+ Permission token: ${token}`,
955
+ `
956
+ Expires at: ${expiresAt}`,
957
+ `
958
+ Use the token to approve or deny this request.`
959
+ );
960
+ return { result: "blocked" };
961
+ }
962
+
963
+ // core/endpoints/bind.ts
964
+ function isEndpoint(value) {
965
+ return typeof value === "function";
966
+ }
967
+ function bindEndpointsRecursively({
968
+ endpoints,
969
+ hooks,
970
+ ctx,
971
+ tree,
972
+ pluginId,
973
+ errorHandlers,
974
+ currentPath = [],
975
+ keyBuilder,
976
+ permissionsConfig,
977
+ endpointMeta,
978
+ database,
979
+ approvalConfig,
980
+ tenantId
981
+ }) {
982
+ for (const [key, value] of Object.entries(endpoints)) {
983
+ const nodeHooks = hooks?.[key];
984
+ if (isEndpoint(value)) {
985
+ const endpointHooks = nodeHooks;
986
+ const operationPath = [...currentPath, key].join(".");
987
+ const boundFn = async (args) => {
988
+ let onPermissionComplete;
989
+ if (permissionsConfig) {
990
+ const meta = endpointMeta?.[operationPath];
991
+ const { result: permResult, onComplete } = await enforcePermission({
992
+ pluginId,
993
+ endpointPath: operationPath,
994
+ args,
995
+ mode: permissionsConfig.mode,
996
+ override: permissionsConfig.overrides?.[operationPath],
997
+ // Default to 'write' when no meta declared — conservative fallback
998
+ riskLevel: meta?.riskLevel ?? "write",
999
+ meta,
1000
+ db: database,
1001
+ timeoutMs: approvalConfig ? parseDurationMs(approvalConfig.timeout) : void 0,
1002
+ tenantId
1003
+ });
1004
+ if (permResult === "blocked") return null;
1005
+ onPermissionComplete = onComplete;
1006
+ }
1007
+ const call = async (attemptNumber, callCtx, callArgs) => {
1008
+ try {
1009
+ return await value(callCtx, callArgs);
1010
+ } catch (error) {
1011
+ if (error instanceof Error) {
1012
+ const retryStrategy = await handleCorsairError(
1013
+ error,
1014
+ pluginId,
1015
+ operationPath,
1016
+ typeof callArgs === "object" && callArgs !== null ? callArgs : { args: callArgs },
1017
+ errorHandlers
1018
+ );
1019
+ if (attemptNumber < (retryStrategy.maxRetries || 0)) {
1020
+ const newAttempt = attemptNumber + 1;
1021
+ console.log(
1022
+ `Retrying (${newAttempt} / ${retryStrategy.maxRetries})...`
1023
+ );
1024
+ let delayMs;
1025
+ if (retryStrategy.headersRetryAfterMs) {
1026
+ delayMs = retryStrategy.headersRetryAfterMs;
1027
+ } else {
1028
+ switch (retryStrategy.retryStrategy) {
1029
+ case "exponential_backoff":
1030
+ delayMs = Math.pow(2, newAttempt - 1) * 1e3;
1031
+ break;
1032
+ case "exponential_backoff_jitter":
1033
+ const baseDelay = Math.pow(2, newAttempt - 1) * 1e3;
1034
+ const jitter = (Math.random() - 0.5) * 1e3;
1035
+ delayMs = Math.max(0, baseDelay + jitter);
1036
+ break;
1037
+ case "linear_1s":
1038
+ delayMs = 1e3;
1039
+ break;
1040
+ case "linear_2s":
1041
+ delayMs = 2e3;
1042
+ break;
1043
+ case "linear_3s":
1044
+ delayMs = 3e3;
1045
+ break;
1046
+ case "linear_4s":
1047
+ delayMs = 4e3;
1048
+ break;
1049
+ default:
1050
+ delayMs = 1e3;
1051
+ break;
1052
+ }
1053
+ }
1054
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
1055
+ await call(newAttempt, callCtx, callArgs);
1056
+ console.log(
1057
+ `[corsair:${pluginId}:${operationPath}] Retry strategy:`,
1058
+ retryStrategy
1059
+ );
1060
+ }
1061
+ }
1062
+ throw error;
1063
+ }
1064
+ };
1065
+ const key2 = keyBuilder ? await keyBuilder(ctx, "endpoint") : void 0;
1066
+ if (!endpointHooks?.before && !endpointHooks?.after) {
1067
+ const res2 = await call(0, { ...ctx, key: key2 }, args);
1068
+ await onPermissionComplete?.();
1069
+ return res2;
1070
+ }
1071
+ const ctxWithKey = { ...ctx, key: key2 };
1072
+ const beforeResult = endpointHooks.before ? await endpointHooks.before(ctxWithKey, args) : {
1073
+ ctx: ctxWithKey,
1074
+ args,
1075
+ continue: true,
1076
+ passToAfter: void 0
1077
+ };
1078
+ if (beforeResult.continue === false) return;
1079
+ const res = await call(0, beforeResult.ctx, beforeResult.args);
1080
+ await endpointHooks.after?.(
1081
+ beforeResult.ctx,
1082
+ res,
1083
+ beforeResult.passToAfter
1084
+ );
1085
+ await onPermissionComplete?.();
1086
+ return res;
1087
+ };
1088
+ tree[key] = boundFn;
1089
+ } else if (value && typeof value === "object") {
1090
+ const nestedTree = {};
1091
+ bindEndpointsRecursively({
1092
+ endpoints: value,
1093
+ hooks: nodeHooks,
1094
+ ctx,
1095
+ tree: nestedTree,
1096
+ pluginId,
1097
+ errorHandlers,
1098
+ currentPath: [...currentPath, key],
1099
+ keyBuilder,
1100
+ permissionsConfig,
1101
+ endpointMeta,
1102
+ database,
1103
+ approvalConfig,
1104
+ tenantId
1105
+ });
1106
+ tree[key] = nestedTree;
1107
+ }
1108
+ }
1109
+ }
1110
+
1111
+ // core/constants.ts
1112
+ var BaseProviders = [
1113
+ "discord",
1114
+ "github",
1115
+ "gmail",
1116
+ "googlecalendar",
1117
+ "googledrive",
1118
+ "googlesheets",
1119
+ "hubspot",
1120
+ "linear",
1121
+ "posthog",
1122
+ "resend",
1123
+ "slack",
1124
+ "spotify",
1125
+ "tavily"
1126
+ ];
1127
+
1128
+ // core/inspect/index.ts
1129
+ function zodToJsonSchema(schema) {
1130
+ const def = schema._def;
1131
+ const typeName = def.typeName;
1132
+ switch (typeName) {
1133
+ case "ZodString":
1134
+ return { type: "string" };
1135
+ case "ZodNumber":
1136
+ return { type: "number" };
1137
+ case "ZodBoolean":
1138
+ return { type: "boolean" };
1139
+ case "ZodNull":
1140
+ return { type: "null" };
1141
+ case "ZodUnknown":
1142
+ case "ZodAny":
1143
+ return {};
1144
+ case "ZodLiteral":
1145
+ return { const: def.value };
1146
+ case "ZodEnum":
1147
+ return { enum: def.values };
1148
+ case "ZodOptional":
1149
+ return zodToJsonSchema(def.innerType);
1150
+ case "ZodNullable": {
1151
+ const inner = zodToJsonSchema(def.innerType);
1152
+ return { anyOf: [inner, { type: "null" }] };
1153
+ }
1154
+ case "ZodArray":
1155
+ return { type: "array", items: zodToJsonSchema(def.type) };
1156
+ case "ZodRecord":
1157
+ return {
1158
+ type: "object",
1159
+ additionalProperties: zodToJsonSchema(def.valueType)
1160
+ };
1161
+ case "ZodObject": {
1162
+ const shape = def.shape();
1163
+ const properties = {};
1164
+ const required = [];
1165
+ for (const [key, val] of Object.entries(shape)) {
1166
+ properties[key] = zodToJsonSchema(val);
1167
+ const fieldTypeName = val._def.typeName;
1168
+ if (fieldTypeName !== "ZodOptional" && fieldTypeName !== "ZodNullable") {
1169
+ required.push(key);
1170
+ }
1171
+ }
1172
+ const result = { type: "object", properties };
1173
+ if (required.length > 0) result.required = required;
1174
+ return result;
1175
+ }
1176
+ case "ZodUnion":
1177
+ return { anyOf: def.options.map(zodToJsonSchema) };
1178
+ case "ZodIntersection":
1179
+ return {
1180
+ allOf: [
1181
+ zodToJsonSchema(def.left),
1182
+ zodToJsonSchema(def.right)
1183
+ ]
1184
+ };
1185
+ case "ZodEffects":
1186
+ return zodToJsonSchema(def.schema);
1187
+ default:
1188
+ return { type: (typeName ?? "unknown").replace("Zod", "").toLowerCase() };
1189
+ }
1190
+ }
1191
+ var STRING_OPERATORS = ["equals", "contains", "startsWith", "endsWith", "in"];
1192
+ var NUMBER_OPERATORS = ["equals", "gt", "gte", "lt", "lte", "in"];
1193
+ var BOOLEAN_OPERATORS = ["equals"];
1194
+ var DATE_OPERATORS = ["equals", "before", "after", "between"];
1195
+ function getSchemaLeafType(schema) {
1196
+ const def = schema._def;
1197
+ const typeName = def.typeName;
1198
+ switch (typeName) {
1199
+ case "ZodOptional":
1200
+ case "ZodNullable":
1201
+ case "ZodDefault":
1202
+ return getSchemaLeafType(def.innerType);
1203
+ case "ZodEffects":
1204
+ return getSchemaLeafType(def.schema);
1205
+ case "ZodString":
1206
+ return "string";
1207
+ case "ZodNumber":
1208
+ return "number";
1209
+ case "ZodBoolean":
1210
+ return "boolean";
1211
+ case "ZodDate":
1212
+ return "date";
1213
+ default:
1214
+ return null;
1215
+ }
1216
+ }
1217
+ function buildFilterableFields(schema) {
1218
+ const def = schema._def;
1219
+ const typeName = def.typeName;
1220
+ if (typeName === "ZodOptional" || typeName === "ZodNullable" || typeName === "ZodDefault") {
1221
+ return buildFilterableFields(def.innerType);
1222
+ }
1223
+ if (typeName === "ZodEffects") {
1224
+ return buildFilterableFields(def.schema);
1225
+ }
1226
+ if (typeName !== "ZodObject") return {};
1227
+ const shape = def.shape();
1228
+ const result = {};
1229
+ for (const [key, fieldSchema] of Object.entries(shape)) {
1230
+ const leafType = getSchemaLeafType(fieldSchema);
1231
+ if (leafType === "string") {
1232
+ result[key] = { type: "string", operators: STRING_OPERATORS };
1233
+ } else if (leafType === "number") {
1234
+ result[key] = { type: "number", operators: NUMBER_OPERATORS };
1235
+ } else if (leafType === "boolean") {
1236
+ result[key] = { type: "boolean", operators: BOOLEAN_OPERATORS };
1237
+ } else if (leafType === "date") {
1238
+ result[key] = { type: "date", operators: DATE_OPERATORS };
1239
+ }
1240
+ }
1241
+ return result;
1242
+ }
1243
+ function findEntityCaseInsensitive(entities, lowercasedName) {
1244
+ for (const [key, schema] of Object.entries(entities)) {
1245
+ if (key.toLowerCase() === lowercasedName) return [key, schema];
1246
+ }
1247
+ return void 0;
1248
+ }
1249
+ function walkEndpointTree(tree, pathParts, result) {
1250
+ for (const [key, value] of Object.entries(tree)) {
1251
+ const current = [...pathParts, key];
1252
+ if (typeof value === "function") {
1253
+ result.push(current.join("."));
1254
+ } else if (value !== null && typeof value === "object") {
1255
+ walkEndpointTree(value, current, result);
1256
+ }
1257
+ }
1258
+ }
1259
+ function isWebhookLeaf(value) {
1260
+ return value !== null && typeof value === "object" && "match" in value && "handler" in value && typeof value.match === "function" && typeof value.handler === "function";
1261
+ }
1262
+ function walkWebhookTree(tree, pathParts, result) {
1263
+ for (const [key, value] of Object.entries(tree)) {
1264
+ const current = [...pathParts, key];
1265
+ if (isWebhookLeaf(value)) {
1266
+ result.push(current.join("."));
1267
+ } else if (value !== null && typeof value === "object") {
1268
+ walkWebhookTree(value, current, result);
1269
+ }
1270
+ }
1271
+ }
1272
+ function resolveWebhookPathOriginalCase(tree, normalizedParts) {
1273
+ if (normalizedParts.length === 0) return null;
1274
+ const [head, ...tail] = normalizedParts;
1275
+ const entry = Object.entries(tree).find(([k]) => k.toLowerCase() === head);
1276
+ if (!entry) return null;
1277
+ const [originalKey, value] = entry;
1278
+ if (tail.length === 0) {
1279
+ return isWebhookLeaf(value) ? [originalKey] : null;
1280
+ }
1281
+ if (value !== null && typeof value === "object" && !isWebhookLeaf(value)) {
1282
+ const rest = resolveWebhookPathOriginalCase(
1283
+ value,
1284
+ tail
1285
+ );
1286
+ if (rest !== null) return [originalKey, ...rest];
1287
+ }
1288
+ return null;
1289
+ }
1290
+ function buildWebhookUsageExample(pluginId, pathParts, responseSchema) {
1291
+ const lines = [];
1292
+ lines.push(`${pluginId}({`);
1293
+ lines.push(` webhookHooks: {`);
1294
+ for (let i = 0; i < pathParts.length; i++) {
1295
+ const indent = " ".repeat(i + 2);
1296
+ lines.push(`${indent}${pathParts[i]}: {`);
1297
+ }
1298
+ const hookIndent = " ".repeat(pathParts.length + 2);
1299
+ const bodyIndent = hookIndent + " ";
1300
+ lines.push(`${hookIndent}before(ctx, args) {`);
1301
+ lines.push(`${bodyIndent}return { ctx, args };`);
1302
+ lines.push(`${hookIndent}},`);
1303
+ lines.push(`${hookIndent}after(ctx, response) {`);
1304
+ if (responseSchema !== null) {
1305
+ const json = JSON.stringify(responseSchema, null, 2);
1306
+ const commentLines = json.split("\n").map(
1307
+ (l, i) => i === 0 ? `${bodyIndent}// response.data: ${l}` : `${bodyIndent}// ${l}`
1308
+ );
1309
+ lines.push(...commentLines);
1310
+ } else {
1311
+ lines.push(
1312
+ `${bodyIndent}// response.data: unknown (register webhookSchemas to see the type)`
1313
+ );
1314
+ }
1315
+ lines.push(`${hookIndent}},`);
1316
+ for (let i = pathParts.length - 1; i >= 0; i--) {
1317
+ const indent = " ".repeat(i + 2);
1318
+ lines.push(`${indent}},`);
1319
+ }
1320
+ lines.push(` },`);
1321
+ lines.push(`})`);
1322
+ return lines.join("\n");
1323
+ }
1324
+ var KNOWN_PLUGIN_IDS = new Set(BaseProviders);
1325
+ function listOperations(plugins, options) {
1326
+ const type = options?.type ?? "api";
1327
+ const pluginId = options?.plugin;
1328
+ if (pluginId !== void 0) {
1329
+ const found = plugins.find((p) => p.id === pluginId);
1330
+ if (!found) {
1331
+ if (KNOWN_PLUGIN_IDS.has(pluginId)) {
1332
+ return `This plugin (${pluginId}) is not configured. Please add it to the Corsair instance to see its associated methods.`;
1333
+ }
1334
+ return listOperations(plugins);
1335
+ }
1336
+ if (type === "webhooks") {
1337
+ if (!found.webhooks) return [];
1338
+ const paths2 = [];
1339
+ walkWebhookTree(found.webhooks, [], paths2);
1340
+ return paths2.map((path) => `${found.id}.webhooks.${path}`);
1341
+ }
1342
+ if (type === "db") {
1343
+ const entities = found.schema?.entities;
1344
+ if (!entities) return [];
1345
+ return Object.keys(entities).map(
1346
+ (entityName) => `${found.id}.db.${entityName}.search`
1347
+ );
1348
+ }
1349
+ if (!found.endpoints) return [];
1350
+ const paths = [];
1351
+ walkEndpointTree(found.endpoints, [], paths);
1352
+ return paths.map((path) => `${found.id}.api.${path.toLowerCase()}`);
1353
+ }
1354
+ const result = {};
1355
+ if (type === "webhooks") {
1356
+ for (const p of plugins) {
1357
+ if (!p.webhooks) continue;
1358
+ const paths = [];
1359
+ walkWebhookTree(p.webhooks, [], paths);
1360
+ result[p.id] = paths.map((path) => `${p.id}.webhooks.${path}`);
1361
+ }
1362
+ } else if (type === "db") {
1363
+ for (const p of plugins) {
1364
+ const entities = p.schema?.entities;
1365
+ if (!entities) continue;
1366
+ result[p.id] = Object.keys(entities).map(
1367
+ (entityName) => `${p.id}.db.${entityName}.search`
1368
+ );
1369
+ }
1370
+ } else {
1371
+ for (const p of plugins) {
1372
+ if (!p.endpoints) continue;
1373
+ const paths = [];
1374
+ walkEndpointTree(p.endpoints, [], paths);
1375
+ result[p.id] = paths.map((path) => `${p.id}.api.${path.toLowerCase()}`);
1376
+ }
1377
+ }
1378
+ return result;
1379
+ }
1380
+ function findEndpointCaseInsensitive(record, lowercasedPath) {
1381
+ if (!record) return void 0;
1382
+ for (const [key, value] of Object.entries(record)) {
1383
+ if (key.toLowerCase() === lowercasedPath) return value;
1384
+ }
1385
+ return void 0;
1386
+ }
1387
+ function getSchema(plugins, path) {
1388
+ const normalised = path.toLowerCase();
1389
+ const dotIndex = normalised.indexOf(".");
1390
+ if (dotIndex !== -1) {
1391
+ const pluginId = normalised.slice(0, dotIndex);
1392
+ const remainder = normalised.slice(dotIndex + 1);
1393
+ const plugin = plugins.find((p) => p.id === pluginId);
1394
+ if (plugin) {
1395
+ if (remainder.startsWith("db.")) {
1396
+ const dbPath = remainder.slice(3);
1397
+ const lastDot = dbPath.lastIndexOf(".");
1398
+ if (lastDot !== -1) {
1399
+ const entityNameLower = dbPath.slice(0, lastDot);
1400
+ const method = dbPath.slice(lastDot + 1);
1401
+ const entities = plugin.schema?.entities;
1402
+ if (method === "search" && entities) {
1403
+ const entry = findEntityCaseInsensitive(entities, entityNameLower);
1404
+ if (entry) {
1405
+ const [entityName, entitySchema] = entry;
1406
+ return {
1407
+ description: `Search ${pluginId} ${entityName} stored in the local database. Returns an array of matching records. Pass limit and offset (numbers) for pagination.`,
1408
+ filters: {
1409
+ entity_id: {
1410
+ type: "string",
1411
+ operators: STRING_OPERATORS
1412
+ },
1413
+ data: buildFilterableFields(entitySchema)
1414
+ }
1415
+ };
1416
+ }
1417
+ }
1418
+ }
1419
+ return {
1420
+ availableMethods: listOperations(plugins, {
1421
+ type: "db"
1422
+ })
1423
+ };
1424
+ }
1425
+ if (remainder.startsWith("webhooks.")) {
1426
+ const webhookPathNormalised = remainder.slice(9);
1427
+ if (plugin.webhooks) {
1428
+ const originalPathParts = resolveWebhookPathOriginalCase(
1429
+ plugin.webhooks,
1430
+ webhookPathNormalised.split(".")
1431
+ );
1432
+ if (originalPathParts !== null) {
1433
+ const originalPath = originalPathParts.join(".");
1434
+ const schemas2 = findEndpointCaseInsensitive(
1435
+ plugin.webhookSchemas,
1436
+ originalPath.toLowerCase()
1437
+ );
1438
+ const responseSchema = schemas2?.response ? zodToJsonSchema(schemas2.response) : null;
1439
+ return {
1440
+ description: schemas2?.description,
1441
+ payload: schemas2?.payload ? zodToJsonSchema(schemas2.payload) : void 0,
1442
+ response: responseSchema ?? void 0,
1443
+ usage: buildWebhookUsageExample(
1444
+ pluginId,
1445
+ originalPathParts,
1446
+ responseSchema
1447
+ )
1448
+ };
1449
+ }
1450
+ }
1451
+ return {
1452
+ availableWebhooks: listOperations(plugins, {
1453
+ type: "webhooks"
1454
+ })
1455
+ };
1456
+ }
1457
+ let endpointPath = remainder;
1458
+ if (endpointPath.startsWith("api.")) {
1459
+ endpointPath = endpointPath.slice(4);
1460
+ }
1461
+ const meta = findEndpointCaseInsensitive(
1462
+ plugin.endpointMeta,
1463
+ endpointPath
1464
+ );
1465
+ const schemas = findEndpointCaseInsensitive(
1466
+ plugin.endpointSchemas,
1467
+ endpointPath
1468
+ );
1469
+ if (meta || schemas) {
1470
+ return {
1471
+ description: meta?.description,
1472
+ riskLevel: meta?.riskLevel,
1473
+ irreversible: meta?.irreversible,
1474
+ input: schemas?.input ? zodToJsonSchema(schemas.input) : void 0,
1475
+ output: schemas?.output ? zodToJsonSchema(schemas.output) : void 0
1476
+ };
1477
+ }
1478
+ }
1479
+ }
1480
+ return {
1481
+ availableMethods: listOperations(plugins)
1482
+ };
1483
+ }
1484
+ function buildInspectMethods(plugins) {
1485
+ return {
1486
+ list_operations(options) {
1487
+ return listOperations(plugins, options);
1488
+ },
1489
+ get_schema(path) {
1490
+ return getSchema(plugins, path);
1491
+ }
1492
+ };
1493
+ }
1494
+
1495
+ // core/webhooks/bind.ts
1496
+ function isWebhook(value) {
1497
+ return value !== null && typeof value === "object" && "match" in value && "handler" in value && typeof value.match === "function" && typeof value.handler === "function";
1498
+ }
1499
+ function bindWebhooksRecursively({
1500
+ webhooks,
1501
+ hooks,
1502
+ ctx,
1503
+ webhooksTree,
1504
+ keyBuilder
1505
+ }) {
1506
+ for (const [key, value] of Object.entries(webhooks)) {
1507
+ const nodeHooks = hooks?.[key];
1508
+ if (isWebhook(value)) {
1509
+ const webhookHooks = nodeHooks;
1510
+ const boundHandler = async (request) => {
1511
+ const call = (callCtx, callRequest) => value.handler(callCtx, callRequest);
1512
+ const key2 = keyBuilder ? await keyBuilder(ctx, "webhook") : void 0;
1513
+ if (!webhookHooks?.before && !webhookHooks?.after) {
1514
+ return call({ ...ctx, key: key2 }, request);
1515
+ }
1516
+ return (async () => {
1517
+ const ctxWithKey = { ...ctx, key: key2 };
1518
+ const beforeResult = webhookHooks.before ? await webhookHooks.before(ctxWithKey, request) : {
1519
+ ctx: ctxWithKey,
1520
+ args: request,
1521
+ continue: true,
1522
+ passToAfter: void 0
1523
+ };
1524
+ if (beforeResult.continue === false) return;
1525
+ const res = await call(beforeResult.ctx, beforeResult.args);
1526
+ if (res?.success === true) {
1527
+ await webhookHooks.after?.(
1528
+ beforeResult.ctx,
1529
+ res,
1530
+ beforeResult.passToAfter
1531
+ );
1532
+ }
1533
+ return res;
1534
+ })();
1535
+ };
1536
+ webhooksTree[key] = {
1537
+ match: value.match,
1538
+ handler: boundHandler
1539
+ };
1540
+ } else if (value && typeof value === "object") {
1541
+ const nestedWebhooksTree = {};
1542
+ bindWebhooksRecursively({
1543
+ webhooks: value,
1544
+ hooks: nodeHooks,
1545
+ ctx,
1546
+ webhooksTree: nestedWebhooksTree,
1547
+ keyBuilder
1548
+ });
1549
+ webhooksTree[key] = nestedWebhooksTree;
1550
+ }
1551
+ }
1552
+ }
1553
+
1554
+ // core/client/index.ts
1555
+ function createAccountIdResolver(database, integrationName, tenantId) {
1556
+ let cachedAccountId = null;
1557
+ return async () => {
1558
+ if (cachedAccountId) return cachedAccountId;
1559
+ if (!database) {
1560
+ throw new Error("Database not configured");
1561
+ }
1562
+ const integration = await database.db.selectFrom("corsair_integrations").selectAll().where("name", "=", integrationName).executeTakeFirst();
1563
+ if (!integration) {
1564
+ throw new Error(
1565
+ `Integration "${integrationName}" not found. Make sure to create the integration first.`
1566
+ );
1567
+ }
1568
+ const account = await database.db.selectFrom("corsair_accounts").selectAll().where("tenant_id", "=", tenantId).where("integration_id", "=", integration.id).executeTakeFirst();
1569
+ if (!account) {
1570
+ throw new Error(
1571
+ `Account not found for tenant "${tenantId}" and integration "${integrationName}". Make sure to create the account first.`
1572
+ );
1573
+ }
1574
+ cachedAccountId = account.id;
1575
+ return cachedAccountId;
1576
+ };
1577
+ }
1578
+ function createEntityClient(database, getAccountId, entityTypeName, version, dataSchema) {
1579
+ if (database) {
1580
+ return createKyselyEntityClient(
1581
+ database.db,
1582
+ getAccountId,
1583
+ entityTypeName,
1584
+ version,
1585
+ dataSchema
1586
+ );
1587
+ }
1588
+ return {
1589
+ findByEntityId: async () => null,
1590
+ findById: async () => null,
1591
+ findManyByEntityIds: async () => [],
1592
+ list: async () => [],
1593
+ search: async () => [],
1594
+ upsertByEntityId: async () => {
1595
+ throw new Error("Database not configured");
1596
+ },
1597
+ deleteById: async () => false,
1598
+ deleteByEntityId: async () => false,
1599
+ count: async () => 0
1600
+ };
1601
+ }
1602
+ function buildCorsairClient(plugins, options) {
1603
+ const { database, tenantId, kek, rootErrorHandlers, approvalConfig } = options;
1604
+ const apiUnsafe = {};
1605
+ const pluginEntitiesUnsafe = {};
1606
+ for (const plugin of plugins) {
1607
+ apiUnsafe[plugin.id] = {};
1608
+ pluginEntitiesUnsafe[plugin.id] = {};
1609
+ }
1610
+ for (const plugin of plugins) {
1611
+ const schema = plugin.schema;
1612
+ const effectiveTenantId = tenantId ?? "default";
1613
+ const getAccountId = createAccountIdResolver(
1614
+ database,
1615
+ plugin.id,
1616
+ effectiveTenantId
1617
+ );
1618
+ if (schema?.entities) {
1619
+ const dbClients = {};
1620
+ for (const [entityTypeName, dataSchema] of Object.entries(
1621
+ schema.entities
1622
+ )) {
1623
+ const entityClient = database ? createKyselyEntityClient(
1624
+ database.db,
1625
+ getAccountId,
1626
+ entityTypeName,
1627
+ schema.version,
1628
+ dataSchema
1629
+ ) : createEntityClient(
1630
+ void 0,
1631
+ getAccountId,
1632
+ entityTypeName,
1633
+ schema.version,
1634
+ dataSchema
1635
+ );
1636
+ dbClients[entityTypeName] = entityClient;
1637
+ }
1638
+ pluginEntitiesUnsafe[plugin.id].db = dbClients;
1639
+ apiUnsafe[plugin.id].db = dbClients;
1640
+ }
1641
+ const pluginOptions = plugin.options;
1642
+ const authConfig = plugin.authConfig;
1643
+ let accountKeyManager;
1644
+ if (database && kek && pluginOptions?.authType) {
1645
+ const extraAccountFields = authConfig?.[pluginOptions.authType]?.account ?? [];
1646
+ accountKeyManager = createAccountKeyManager({
1647
+ authType: pluginOptions.authType,
1648
+ integrationName: plugin.id,
1649
+ tenantId: effectiveTenantId,
1650
+ kek,
1651
+ database,
1652
+ extraAccountFields
1653
+ });
1654
+ apiUnsafe[plugin.id].keys = accountKeyManager;
1655
+ }
1656
+ const ctxForPlugin = {
1657
+ database,
1658
+ db: pluginEntitiesUnsafe[plugin.id]?.db ?? {},
1659
+ $getAccountId: getAccountId,
1660
+ ...plugin.options ? { options: plugin.options } : {},
1661
+ // Include keys manager and authType in context so keyBuilder can access and narrow types
1662
+ ...accountKeyManager ? { keys: accountKeyManager, authType: pluginOptions?.authType } : {},
1663
+ // Include tenantId in context so it's available in webhook hooks
1664
+ ...tenantId ? { tenantId } : {}
1665
+ };
1666
+ const endpoints = plugin.endpoints ?? {};
1667
+ const hooks = plugin.hooks;
1668
+ const allErrorHandlers = {
1669
+ ...rootErrorHandlers,
1670
+ ...plugin.errorHandlers
1671
+ };
1672
+ const boundTree = {};
1673
+ const pluginPermsConfig = plugin.options?.permissions;
1674
+ bindEndpointsRecursively({
1675
+ endpoints,
1676
+ hooks,
1677
+ ctx: ctxForPlugin,
1678
+ tree: boundTree,
1679
+ pluginId: plugin.id,
1680
+ errorHandlers: allErrorHandlers,
1681
+ currentPath: [],
1682
+ keyBuilder: plugin.keyBuilder,
1683
+ permissionsConfig: pluginPermsConfig,
1684
+ // endpointMeta is typed with plugin-specific literal keys — cast to runtime Record
1685
+ endpointMeta: plugin.endpointMeta,
1686
+ database,
1687
+ approvalConfig,
1688
+ tenantId
1689
+ });
1690
+ if (Object.keys(boundTree).length > 0) {
1691
+ apiUnsafe[plugin.id].api = boundTree;
1692
+ }
1693
+ ctxForPlugin.endpoints = boundTree;
1694
+ const webhooks = plugin.webhooks ?? {};
1695
+ const webhookHooks = plugin.webhookHooks;
1696
+ if (Object.keys(webhooks).length > 0) {
1697
+ const boundWebhooks = {};
1698
+ bindWebhooksRecursively({
1699
+ webhooks,
1700
+ hooks: webhookHooks,
1701
+ ctx: ctxForPlugin,
1702
+ webhooksTree: boundWebhooks,
1703
+ keyBuilder: plugin.keyBuilder
1704
+ });
1705
+ apiUnsafe[plugin.id].webhooks = boundWebhooks;
1706
+ if (plugin.pluginWebhookMatcher) {
1707
+ apiUnsafe[plugin.id].pluginWebhookMatcher = plugin.pluginWebhookMatcher;
1708
+ }
1709
+ }
1710
+ }
1711
+ const api = apiUnsafe;
1712
+ const inspect = buildInspectMethods(plugins);
1713
+ return {
1714
+ ...api,
1715
+ ...inspect
1716
+ };
1717
+ }
1718
+ function buildIntegrationKeys(plugins, database, kek) {
1719
+ const keysUnsafe = {};
1720
+ for (const plugin of plugins) {
1721
+ const pluginOptions = plugin.options;
1722
+ const authConfig = plugin.authConfig;
1723
+ if (pluginOptions?.authType) {
1724
+ const extraIntegrationFields = authConfig?.[pluginOptions.authType]?.integration ?? [];
1725
+ const integrationKeyManager = createIntegrationKeyManager({
1726
+ authType: pluginOptions.authType,
1727
+ integrationName: plugin.id,
1728
+ kek,
1729
+ database,
1730
+ extraIntegrationFields
1731
+ });
1732
+ keysUnsafe[plugin.id] = integrationKeyManager;
1733
+ }
1734
+ }
1735
+ return keysUnsafe;
1736
+ }
1737
+
1738
+ // core/index.ts
1739
+ var CORSAIR_INTERNAL = Symbol.for("corsair:internal");
1740
+ function createCorsair(config) {
1741
+ const resolvedDatabase = config.database ? createCorsairDatabase(config.database) : void 0;
1742
+ const integrationKeys = resolvedDatabase && config.kek ? buildIntegrationKeys(config.plugins, resolvedDatabase, config.kek) : createMissingConfigProxy(
1743
+ !!resolvedDatabase,
1744
+ !!config.kek
1745
+ );
1746
+ const internalConfig = {
1747
+ plugins: config.plugins,
1748
+ database: resolvedDatabase,
1749
+ kek: config.kek,
1750
+ multiTenancy: !!config.multiTenancy,
1751
+ approval: config.approval
1752
+ };
1753
+ const permissions = buildPermissionsNamespace(resolvedDatabase);
1754
+ if (config.multiTenancy) {
1755
+ return Object.assign(
1756
+ {
1757
+ withTenant: (tenantId) => {
1758
+ if (!tenantId) {
1759
+ throw new Error(
1760
+ "corsair.withTenant(tenantId): tenantId must be a non-empty string"
1761
+ );
1762
+ }
1763
+ return buildCorsairClient(config.plugins, {
1764
+ database: resolvedDatabase,
1765
+ tenantId,
1766
+ kek: config.kek,
1767
+ rootErrorHandlers: config.errorHandlers,
1768
+ approvalConfig: config.approval
1769
+ });
1770
+ },
1771
+ keys: integrationKeys,
1772
+ permissions,
1773
+ ...buildInspectMethods(config.plugins)
1774
+ },
1775
+ { [CORSAIR_INTERNAL]: internalConfig }
1776
+ );
1777
+ }
1778
+ const client = buildCorsairClient(config.plugins, {
1779
+ database: resolvedDatabase,
1780
+ tenantId: void 0,
1781
+ kek: config.kek,
1782
+ rootErrorHandlers: config.errorHandlers,
1783
+ approvalConfig: config.approval
1784
+ });
1785
+ return Object.assign({}, client, {
1786
+ keys: integrationKeys,
1787
+ permissions,
1788
+ [CORSAIR_INTERNAL]: internalConfig
1789
+ });
1790
+ }
1791
+ export {
1792
+ BASE_AUTH_FIELDS,
1793
+ CORSAIR_INTERNAL,
1794
+ createAccountKeyManager,
1795
+ createCorsair,
1796
+ createIntegrationKeyManager,
1797
+ decryptConfig,
1798
+ decryptDEK,
1799
+ decryptWithDEK,
1800
+ encryptConfig,
1801
+ encryptDEK,
1802
+ encryptWithDEK,
1803
+ generateDEK,
1804
+ initializeAccountDEK,
1805
+ initializeIntegrationDEK,
1806
+ reEncryptConfig
1807
+ };