@mycreat-labs/integrations-sdk 0.0.0-stage → 0.1.0

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 (54) hide show
  1. package/README.md +54 -2
  2. package/dist/adapters/bullmq.cjs +110 -0
  3. package/dist/adapters/bullmq.cjs.map +1 -0
  4. package/dist/adapters/bullmq.d.cts +11 -0
  5. package/dist/adapters/bullmq.d.ts +11 -0
  6. package/dist/adapters/bullmq.js +85 -0
  7. package/dist/adapters/bullmq.js.map +1 -0
  8. package/dist/adapters/postgres.cjs +396 -0
  9. package/dist/adapters/postgres.cjs.map +1 -0
  10. package/dist/adapters/postgres.d.cts +51 -0
  11. package/dist/adapters/postgres.d.ts +51 -0
  12. package/dist/adapters/postgres.js +368 -0
  13. package/dist/adapters/postgres.js.map +1 -0
  14. package/dist/adapters/prisma.cjs +414 -0
  15. package/dist/adapters/prisma.cjs.map +1 -0
  16. package/dist/adapters/prisma.d.cts +15 -0
  17. package/dist/adapters/prisma.d.ts +15 -0
  18. package/dist/adapters/prisma.js +386 -0
  19. package/dist/adapters/prisma.js.map +1 -0
  20. package/dist/adapters/redis.cjs +125 -0
  21. package/dist/adapters/redis.cjs.map +1 -0
  22. package/dist/adapters/redis.d.cts +20 -0
  23. package/dist/adapters/redis.d.ts +20 -0
  24. package/dist/adapters/redis.js +98 -0
  25. package/dist/adapters/redis.js.map +1 -0
  26. package/dist/errors-3hDEtzuZ.d.cts +11 -0
  27. package/dist/errors-3hDEtzuZ.d.ts +11 -0
  28. package/dist/index.cjs +1328 -0
  29. package/dist/index.cjs.map +1 -0
  30. package/dist/index.d.cts +128 -0
  31. package/dist/index.d.ts +128 -0
  32. package/dist/index.js +1297 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/providers/index.cjs +1287 -0
  35. package/dist/providers/index.cjs.map +1 -0
  36. package/dist/providers/index.d.cts +144 -0
  37. package/dist/providers/index.d.ts +144 -0
  38. package/dist/providers/index.js +1252 -0
  39. package/dist/providers/index.js.map +1 -0
  40. package/dist/testing.cjs +275 -0
  41. package/dist/testing.cjs.map +1 -0
  42. package/dist/testing.d.cts +20 -0
  43. package/dist/testing.d.ts +20 -0
  44. package/dist/testing.js +248 -0
  45. package/dist/testing.js.map +1 -0
  46. package/dist/types-Y4-DoJ1A.d.cts +260 -0
  47. package/dist/types-Y4-DoJ1A.d.ts +260 -0
  48. package/docs/migration.md +23 -0
  49. package/docs/security.md +26 -0
  50. package/docs/verification.md +22 -0
  51. package/examples/embedded-host.ts +62 -0
  52. package/migrations/0001_initial.sql +40 -0
  53. package/package.json +90 -4
  54. package/schema.prisma +80 -0
@@ -0,0 +1,1287 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/providers/index.ts
21
+ var providers_exports = {};
22
+ __export(providers_exports, {
23
+ NOTION_API_VERSION: () => NOTION_API_VERSION,
24
+ ProviderHttpError: () => ProviderHttpError,
25
+ RAINDROP_TOKEN_ENDPOINT: () => RAINDROP_TOKEN_ENDPOINT,
26
+ createAppleProvider: () => createAppleProvider,
27
+ createGitHubProvider: () => createGitHubProvider,
28
+ createGoogleProvider: () => createGoogleProvider,
29
+ createNotionClient: () => createNotionClient,
30
+ createNotionProvider: () => createNotionProvider,
31
+ createRaindropClient: () => createRaindropClient,
32
+ createRaindropProvider: () => createRaindropProvider,
33
+ notionRequest: () => notionRequest,
34
+ raindropRequest: () => raindropRequest,
35
+ validateNotionCreatePage: () => validateNotionCreatePage,
36
+ validateRaindropCreateBookmark: () => validateRaindropCreateBookmark
37
+ });
38
+ module.exports = __toCommonJS(providers_exports);
39
+
40
+ // src/providers/identity.ts
41
+ var import_jose = require("jose");
42
+ var import_node_crypto = require("crypto");
43
+
44
+ // src/errors.ts
45
+ var IntegrationError = class extends Error {
46
+ constructor(code, disposition = "permanent_failure", retryAfterMs) {
47
+ super(code);
48
+ this.code = code;
49
+ this.disposition = disposition;
50
+ this.retryAfterMs = retryAfterMs;
51
+ this.name = "IntegrationError";
52
+ }
53
+ code;
54
+ disposition;
55
+ retryAfterMs;
56
+ sdkError = "@mycreat-labs/integrations-sdk/v1";
57
+ };
58
+
59
+ // src/providers/http.ts
60
+ var ProviderHttpError = class extends IntegrationError {
61
+ status;
62
+ providerCode;
63
+ definite;
64
+ committedResourceId;
65
+ committedChildIds;
66
+ constructor(code, disposition, options = {}) {
67
+ super(code, disposition, options.retryAfterMs);
68
+ this.name = "ProviderHttpError";
69
+ this.status = options.status ?? null;
70
+ this.providerCode = options.providerCode;
71
+ this.definite = options.definite ?? false;
72
+ this.committedResourceId = options.committedResourceId;
73
+ this.committedChildIds = options.committedChildIds;
74
+ }
75
+ };
76
+ function record(value) {
77
+ if (!value || typeof value !== "object" || Array.isArray(value))
78
+ throw new IntegrationError("invalid_response");
79
+ return value;
80
+ }
81
+ function text(value, max = 4096) {
82
+ if (typeof value !== "string" || value.length === 0 || value.length > max)
83
+ throw new IntegrationError("invalid_response");
84
+ return value;
85
+ }
86
+ function optionalText(value, max = 4096) {
87
+ return value === void 0 || value === null ? void 0 : text(value, max);
88
+ }
89
+ function inputRecord(value, keys2) {
90
+ if (!value || typeof value !== "object" || Array.isArray(value))
91
+ throw new IntegrationError("invalid_input");
92
+ const input = value;
93
+ if (Object.keys(input).some((key) => !keys2.includes(key)))
94
+ throw new IntegrationError("invalid_input");
95
+ return input;
96
+ }
97
+ function inputText(value, maximum, required = true) {
98
+ if (value === void 0 && !required) return void 0;
99
+ if (typeof value !== "string" || required && !value.trim() || value.length > maximum)
100
+ throw new IntegrationError("invalid_input");
101
+ return value;
102
+ }
103
+ function webUrl(value) {
104
+ const result = inputText(value, 1e4);
105
+ try {
106
+ const url = new URL(result);
107
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password)
108
+ throw new Error();
109
+ } catch {
110
+ throw new IntegrationError("invalid_input");
111
+ }
112
+ return result;
113
+ }
114
+ function jsonMetadata(value) {
115
+ if (value === void 0) return void 0;
116
+ const visit = (item, depth) => depth <= 10 && (item === null || typeof item === "string" || typeof item === "boolean" || typeof item === "number" && Number.isFinite(item) || Array.isArray(item) && item.every((child) => visit(child, depth + 1)) || typeof item === "object" && item !== null && Object.getPrototypeOf(item) === Object.prototype && Object.values(item).every((child) => visit(child, depth + 1)));
117
+ if (!visit(value, 0) || JSON.stringify(value).length > 16384)
118
+ throw new IntegrationError("invalid_input");
119
+ return value;
120
+ }
121
+ var safeCodes = /* @__PURE__ */ new Set([
122
+ "invalid_client",
123
+ "unauthorized_client",
124
+ "invalid_grant",
125
+ "bad_authorization_code",
126
+ "invalid_request",
127
+ "invalid_json",
128
+ "validation_error",
129
+ "restricted_resource",
130
+ "unauthorized",
131
+ "object_not_found",
132
+ "rate_limited",
133
+ "conflict_error",
134
+ "internal_server_error",
135
+ "service_unavailable",
136
+ "gateway_timeout",
137
+ "service_overload",
138
+ "row_limit_exceeded",
139
+ "invalid_token",
140
+ "bad_verification_code",
141
+ "incorrect_client_credentials",
142
+ "redirect_uri_mismatch",
143
+ "access_denied"
144
+ ]);
145
+ var uuid = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
146
+ var isUuid = (value) => typeof value === "string" && uuid.test(value);
147
+ function retryDelay(value) {
148
+ if (!value) return void 0;
149
+ const milliseconds = /^\d+(?:\.\d+)?$/.test(value) ? Number(value) * 1e3 : Date.parse(value) - Date.now();
150
+ return Number.isFinite(milliseconds) ? Math.max(0, Math.min(milliseconds, 864e5)) : void 0;
151
+ }
152
+ function failure(status, body, write, retryAfterMs) {
153
+ const payload = body && typeof body === "object" && !Array.isArray(body) ? body : {};
154
+ const vendorCode = typeof payload.code === "string" ? payload.code : typeof payload.error === "string" ? payload.error : void 0;
155
+ const providerCode = vendorCode && safeCodes.has(vendorCode) ? vendorCode : void 0;
156
+ const extra = payload.additional_data && typeof payload.additional_data === "object" ? payload.additional_data : {};
157
+ const committedResourceId = isUuid(extra.committed_resource_id) ? extra.committed_resource_id : void 0;
158
+ const committedChildIds = Array.isArray(extra.committed_child_ids) && extra.committed_child_ids.length <= 100 && extra.committed_child_ids.every(isUuid) ? extra.committed_child_ids : void 0;
159
+ const definite = status >= 400 && status < 500 && status !== 408 && !committedResourceId && !committedChildIds?.length;
160
+ let code = status === 401 ? "auth_required" : status === 404 ? "resource_unavailable" : status === 403 ? "permission_required" : status === 429 || status === 529 ? "rate_limited" : status >= 500 ? "upstream_unavailable" : "invalid_payload";
161
+ if (providerCode === "invalid_client" || providerCode === "unauthorized_client" || providerCode === "incorrect_client_credentials" || payload.errorMessage === "client_id or client_secret is invalid")
162
+ code = "application_unavailable";
163
+ else if (providerCode === "invalid_grant" || providerCode === "bad_authorization_code" || providerCode === "invalid_token" || providerCode === "bad_verification_code")
164
+ code = "auth_required";
165
+ else if (extra.rate_limit_reason === "public_api_request_blocked")
166
+ code = "access_restricted";
167
+ else if (status === 403 && providerCode === "restricted_resource")
168
+ code = "resource_unavailable";
169
+ else if (status === 403 && typeof payload.message === "string" && /block.*limit|limit.*block/i.test(payload.message))
170
+ code = "workspace_capacity";
171
+ const disposition = code === "auth_required" ? "requires_reauth" : write && !definite ? "outcome_unknown" : code === "rate_limited" || status >= 500 || status === 408 ? "retryable" : "permanent_failure";
172
+ return new ProviderHttpError(code, disposition, {
173
+ status,
174
+ providerCode,
175
+ definite,
176
+ retryAfterMs,
177
+ committedResourceId,
178
+ committedChildIds
179
+ });
180
+ }
181
+ async function requestJson(url, init, fetcher, options = {}) {
182
+ const write = options.write ?? !["GET", "HEAD"].includes(init.method ?? "GET");
183
+ const signal = init.signal ? AbortSignal.any([init.signal, AbortSignal.timeout(3e4)]) : AbortSignal.timeout(3e4);
184
+ let response;
185
+ try {
186
+ response = await fetcher(url, { ...init, signal, redirect: "error" });
187
+ } catch (error) {
188
+ if (error instanceof Error && "sdkError" in error && error.sdkError === "@mycreat-labs/integrations-sdk/v1" && "code" in error && error.code === "rate_limited" && "definite" in error && error.definite === true) {
189
+ const delay = "retryAfterMs" in error && typeof error.retryAfterMs === "number" && Number.isFinite(error.retryAfterMs) ? Math.max(0, Math.min(error.retryAfterMs, 864e5)) : void 0;
190
+ throw new ProviderHttpError("rate_limited", "retryable", {
191
+ status: 429,
192
+ definite: true,
193
+ retryAfterMs: delay
194
+ });
195
+ }
196
+ throw new ProviderHttpError(
197
+ "upstream_unavailable",
198
+ write ? "outcome_unknown" : "retryable"
199
+ );
200
+ }
201
+ if (response.status === 204 && response.ok) return {};
202
+ let body;
203
+ try {
204
+ if (options.requireJson && !response.headers.get("content-type")?.toLowerCase().includes("json"))
205
+ throw new Error();
206
+ if (!response.body) throw new Error();
207
+ const reader = response.body.getReader();
208
+ const chunks = [];
209
+ let size = 0;
210
+ try {
211
+ for (; ; ) {
212
+ const part = await reader.read();
213
+ if (part.done) break;
214
+ size += part.value.length;
215
+ if (size > 2097152) {
216
+ await reader.cancel();
217
+ throw new Error();
218
+ }
219
+ chunks.push(part.value);
220
+ }
221
+ } finally {
222
+ reader.releaseLock();
223
+ }
224
+ body = JSON.parse(Buffer.concat(chunks).toString("utf8"));
225
+ } catch {
226
+ if (!response.ok)
227
+ throw failure(
228
+ response.status,
229
+ {},
230
+ write,
231
+ retryDelay(response.headers.get("retry-after"))
232
+ );
233
+ throw new ProviderHttpError(
234
+ "invalid_response",
235
+ write ? "outcome_unknown" : "retryable",
236
+ { status: response.status }
237
+ );
238
+ }
239
+ if (!response.ok)
240
+ throw failure(
241
+ response.status,
242
+ body,
243
+ write,
244
+ retryDelay(response.headers.get("retry-after"))
245
+ );
246
+ if (body && typeof body === "object" && !Array.isArray(body)) {
247
+ const value = body;
248
+ if (typeof value.error === "string" || options.raindrop && value.result === false) {
249
+ const status = typeof value.status === "number" && Number.isInteger(value.status) && value.status >= 400 && value.status < 600 ? value.status : 400;
250
+ throw failure(
251
+ status,
252
+ body,
253
+ write,
254
+ retryDelay(response.headers.get("retry-after"))
255
+ );
256
+ }
257
+ }
258
+ return body;
259
+ }
260
+ function providerPath(path, prefix) {
261
+ if (path.length > 4096 || !/^\/[a-zA-Z0-9_/-]+(?:\?[a-zA-Z0-9_%=&.~-]+)?$/.test(path) || path.includes("//") || path.includes(".."))
262
+ throw new ProviderHttpError("invalid_upstream_path", "permanent_failure", {
263
+ status: 400,
264
+ definite: true
265
+ });
266
+ return path.startsWith(`${prefix}/`) || path === prefix ? path : `${prefix}${path}`;
267
+ }
268
+ function notionRequest(path, options) {
269
+ const normalized = providerPath(path, "/v1");
270
+ if (!options.accessToken && (!options.clientId || !options.clientSecret))
271
+ throw new ProviderHttpError(
272
+ "application_unavailable",
273
+ "permanent_failure",
274
+ { definite: true }
275
+ );
276
+ const authorization = options.accessToken ? `Bearer ${options.accessToken}` : `Basic ${Buffer.from(`${options.clientId}:${options.clientSecret}`).toString("base64")}`;
277
+ return requestJson(
278
+ `https://api.notion.com${normalized}`,
279
+ {
280
+ method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
281
+ headers: {
282
+ "Notion-Version": options.apiVersion ?? "2026-03-11",
283
+ "Content-Type": "application/json",
284
+ Accept: "application/json",
285
+ Authorization: authorization
286
+ },
287
+ ...options.body === void 0 ? {} : { body: JSON.stringify(options.body) },
288
+ ...options.signal ? { signal: options.signal } : {}
289
+ },
290
+ options.fetch ?? globalThis.fetch,
291
+ {
292
+ write: normalized !== "/v1/search" && (options.method ?? (options.body === void 0 ? "GET" : "POST")) !== "GET"
293
+ }
294
+ );
295
+ }
296
+ function raindropRequest(path, options) {
297
+ const normalized = path === "/v1/oauth/access_token" ? path : providerPath(path, "/rest/v1");
298
+ if (normalized !== "/v1/oauth/access_token" && !options.accessToken)
299
+ throw new ProviderHttpError("auth_required", "requires_reauth", {
300
+ status: 401,
301
+ definite: true
302
+ });
303
+ return requestJson(
304
+ `https://api.raindrop.io${normalized}`,
305
+ {
306
+ method: options.method ?? (options.body === void 0 ? "GET" : "POST"),
307
+ headers: {
308
+ "Content-Type": "application/json",
309
+ Accept: "application/json",
310
+ ...options.accessToken ? { Authorization: `Bearer ${options.accessToken}` } : {}
311
+ },
312
+ ...options.body === void 0 ? {} : { body: JSON.stringify(options.body) },
313
+ ...options.signal ? { signal: options.signal } : {}
314
+ },
315
+ options.fetch ?? globalThis.fetch,
316
+ { raindrop: true, requireJson: true }
317
+ );
318
+ }
319
+
320
+ // src/providers/identity.ts
321
+ function configure(config) {
322
+ if (!config.registrationId || !config.clientId || !config.redirectUri || config.identityNamespace === "")
323
+ throw new IntegrationError("invalid_provider_configuration");
324
+ let url;
325
+ try {
326
+ url = new URL(config.redirectUri);
327
+ } catch {
328
+ throw new IntegrationError("invalid_provider_configuration");
329
+ }
330
+ if (url.username || url.password || url.hash || url.protocol !== "https:" && !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))
331
+ throw new IntegrationError("invalid_provider_configuration");
332
+ }
333
+ function identityPurpose(purpose) {
334
+ if (purpose !== "identity") throw new IntegrationError("unsupported_purpose");
335
+ }
336
+ function authorizationURL(base, parameters) {
337
+ const url = new URL(base);
338
+ for (const [key, value] of Object.entries(parameters))
339
+ url.searchParams.set(key, value);
340
+ return url.toString();
341
+ }
342
+ var keyCaches = /* @__PURE__ */ new WeakMap();
343
+ var keyLoads = /* @__PURE__ */ new WeakMap();
344
+ async function keys(url, fetcher, force = false) {
345
+ let cache = keyCaches.get(fetcher);
346
+ if (!cache) {
347
+ cache = /* @__PURE__ */ new Map();
348
+ keyCaches.set(fetcher, cache);
349
+ }
350
+ const existing = cache.get(url);
351
+ if (!force && existing && existing.expires > Date.now())
352
+ return existing.resolve;
353
+ if (force && existing && existing.refreshedAt + 3e4 > Date.now())
354
+ return existing.resolve;
355
+ let loads = keyLoads.get(fetcher);
356
+ if (!loads) {
357
+ loads = /* @__PURE__ */ new Map();
358
+ keyLoads.set(fetcher, loads);
359
+ }
360
+ const pending = loads.get(url);
361
+ if (pending) return pending;
362
+ const load = (async () => {
363
+ try {
364
+ const value = record(await requestJson(url, {}, fetcher));
365
+ if (!Array.isArray(value.keys) || value.keys.length > 100)
366
+ throw new Error();
367
+ const resolve = (0, import_jose.createLocalJWKSet)(value);
368
+ const now = Date.now();
369
+ cache.set(url, { expires: now + 3e5, refreshedAt: now, resolve });
370
+ return resolve;
371
+ } catch {
372
+ throw new IntegrationError("identity_unavailable", "retryable");
373
+ }
374
+ })();
375
+ loads.set(url, load);
376
+ try {
377
+ return await load;
378
+ } finally {
379
+ loads.delete(url);
380
+ }
381
+ }
382
+ async function verifyToken(input, config, fetcher, options) {
383
+ if (!input.nonce || input.nonce.length > 1024 || !input.idToken || input.idToken.length > 32768 || !options.audiences.length || options.audiences.some((item) => !item))
384
+ throw new IntegrationError("invalid_identity");
385
+ const google = options.provider === "google";
386
+ const issuer = google ? "https://accounts.google.com" : "https://appleid.apple.com";
387
+ const jwks = google ? "https://www.googleapis.com/oauth2/v3/certs" : "https://appleid.apple.com/auth/keys";
388
+ const verificationOptions = {
389
+ algorithms: ["RS256"],
390
+ issuer: google ? [issuer, "accounts.google.com"] : issuer,
391
+ audience: [...options.audiences],
392
+ requiredClaims: ["sub", "exp", "iat", "nonce"],
393
+ clockTolerance: 30
394
+ };
395
+ let payload;
396
+ try {
397
+ let resolve = await keys(jwks, fetcher);
398
+ try {
399
+ ({ payload } = await (0, import_jose.jwtVerify)(
400
+ input.idToken,
401
+ resolve,
402
+ verificationOptions
403
+ ));
404
+ } catch (error) {
405
+ if (!(error && typeof error === "object" && "code" in error && error.code === "ERR_JWKS_NO_MATCHING_KEY"))
406
+ throw error;
407
+ resolve = await keys(jwks, fetcher, true);
408
+ ({ payload } = await (0, import_jose.jwtVerify)(
409
+ input.idToken,
410
+ resolve,
411
+ verificationOptions
412
+ ));
413
+ }
414
+ const expected = Buffer.from(input.nonce);
415
+ const actual = Buffer.from(
416
+ typeof payload.nonce === "string" ? payload.nonce : ""
417
+ );
418
+ if (expected.length !== actual.length || !(0, import_node_crypto.timingSafeEqual)(expected, actual) || typeof payload.iat !== "number" || payload.iat > Date.now() / 1e3 + 30 || typeof payload.sub !== "string" || !payload.sub || payload.sub.length > 255)
419
+ throw new Error();
420
+ if (Array.isArray(payload.aud) && payload.aud.length > 1 && (typeof payload.azp !== "string" || !options.audiences.includes(payload.azp)))
421
+ throw new Error();
422
+ } catch (error) {
423
+ if (error instanceof IntegrationError && (error.disposition === "retryable" || error.disposition === "outcome_unknown"))
424
+ throw new IntegrationError("identity_unavailable", "retryable");
425
+ throw new IntegrationError("invalid_identity");
426
+ }
427
+ const email = optionalText(payload.email, 320);
428
+ const name = optionalText(payload.name, 1024);
429
+ return {
430
+ provider: options.provider,
431
+ issuer,
432
+ subject: payload.sub,
433
+ identityNamespace: config.identityNamespace ?? config.registrationId,
434
+ emailVerified: payload.email_verified === true || options.provider === "apple" && payload.email_verified === "true",
435
+ ...email ? { email } : {},
436
+ ...name ? { name } : {}
437
+ };
438
+ }
439
+ function createGoogleProvider(config) {
440
+ configure(config);
441
+ return {
442
+ key: "google",
443
+ registrationId: config.registrationId,
444
+ purposes: ["identity"],
445
+ authorization: {
446
+ url(input) {
447
+ identityPurpose(input.purpose);
448
+ return authorizationURL(
449
+ "https://accounts.google.com/o/oauth2/v2/auth",
450
+ {
451
+ client_id: config.clientId,
452
+ redirect_uri: config.redirectUri,
453
+ response_type: "code",
454
+ scope: "openid email profile",
455
+ state: input.state,
456
+ nonce: input.nonce,
457
+ code_challenge: input.challenge,
458
+ code_challenge_method: "S256"
459
+ }
460
+ );
461
+ },
462
+ async exchange(input, fetcher) {
463
+ identityPurpose(input.purpose);
464
+ if (!config.clientSecret)
465
+ throw new IntegrationError("application_unavailable");
466
+ const tokens = record(
467
+ await requestJson(
468
+ "https://oauth2.googleapis.com/token",
469
+ {
470
+ method: "POST",
471
+ headers: {
472
+ "Content-Type": "application/x-www-form-urlencoded",
473
+ Accept: "application/json"
474
+ },
475
+ body: new URLSearchParams({
476
+ client_id: config.clientId,
477
+ client_secret: config.clientSecret,
478
+ redirect_uri: config.redirectUri,
479
+ grant_type: "authorization_code",
480
+ code: input.code,
481
+ code_verifier: input.verifier
482
+ }),
483
+ ...input.signal ? { signal: input.signal } : {}
484
+ },
485
+ fetcher
486
+ )
487
+ );
488
+ const identity = await verifyToken(
489
+ { idToken: text(tokens.id_token, 32768), nonce: input.nonce },
490
+ config,
491
+ fetcher,
492
+ { provider: "google", audiences: [config.clientId] }
493
+ );
494
+ return { identity };
495
+ }
496
+ },
497
+ verifyNative(input, fetcher) {
498
+ return verifyToken(input, config, fetcher, {
499
+ provider: "google",
500
+ audiences: config.nativeClientIds ?? [config.clientId]
501
+ });
502
+ }
503
+ };
504
+ }
505
+ function createAppleProvider(config) {
506
+ configure(config);
507
+ async function clientSecret() {
508
+ if (!config.teamId || !config.keyId || !config.privateKey)
509
+ throw new IntegrationError("application_unavailable");
510
+ try {
511
+ const key = await (0, import_jose.importPKCS8)(config.privateKey, "ES256");
512
+ return await new import_jose.SignJWT({}).setProtectedHeader({ alg: "ES256", kid: config.keyId }).setIssuer(config.teamId).setSubject(config.clientId).setAudience("https://appleid.apple.com").setIssuedAt().setExpirationTime("5m").sign(key);
513
+ } catch {
514
+ throw new IntegrationError("application_unavailable");
515
+ }
516
+ }
517
+ return {
518
+ key: "apple",
519
+ registrationId: config.registrationId,
520
+ purposes: ["identity"],
521
+ authorization: {
522
+ url(input) {
523
+ identityPurpose(input.purpose);
524
+ return authorizationURL("https://appleid.apple.com/auth/authorize", {
525
+ client_id: config.clientId,
526
+ redirect_uri: config.redirectUri,
527
+ response_type: "code",
528
+ response_mode: "form_post",
529
+ scope: "name email",
530
+ state: input.state,
531
+ nonce: input.nonce
532
+ });
533
+ },
534
+ async exchange(input, fetcher) {
535
+ identityPurpose(input.purpose);
536
+ const result = record(
537
+ await requestJson(
538
+ "https://appleid.apple.com/auth/token",
539
+ {
540
+ method: "POST",
541
+ headers: {
542
+ "Content-Type": "application/x-www-form-urlencoded",
543
+ Accept: "application/json"
544
+ },
545
+ body: new URLSearchParams({
546
+ client_id: config.clientId,
547
+ client_secret: await clientSecret(),
548
+ redirect_uri: config.redirectUri,
549
+ grant_type: "authorization_code",
550
+ code: input.code
551
+ }),
552
+ ...input.signal ? { signal: input.signal } : {}
553
+ },
554
+ fetcher
555
+ )
556
+ );
557
+ return {
558
+ identity: await verifyToken(
559
+ { idToken: text(result.id_token, 32768), nonce: input.nonce },
560
+ config,
561
+ fetcher,
562
+ { provider: "apple", audiences: [config.clientId] }
563
+ )
564
+ };
565
+ }
566
+ },
567
+ // nonce is the actual expected token nonce; a native host using SHA256 supplies that hash.
568
+ verifyNative(input, fetcher) {
569
+ return verifyToken(input, config, fetcher, {
570
+ provider: "apple",
571
+ audiences: config.nativeClientIds ?? [config.clientId]
572
+ });
573
+ }
574
+ };
575
+ }
576
+ function createGitHubProvider(config) {
577
+ configure(config);
578
+ if (!config.clientSecret)
579
+ throw new IntegrationError("invalid_provider_configuration");
580
+ return {
581
+ key: "github",
582
+ registrationId: config.registrationId,
583
+ purposes: ["identity"],
584
+ authorization: {
585
+ url(input) {
586
+ identityPurpose(input.purpose);
587
+ return authorizationURL("https://github.com/login/oauth/authorize", {
588
+ client_id: config.clientId,
589
+ redirect_uri: config.redirectUri,
590
+ scope: "read:user user:email",
591
+ state: input.state,
592
+ code_challenge: input.challenge,
593
+ code_challenge_method: "S256"
594
+ });
595
+ },
596
+ async exchange(input, fetcher) {
597
+ identityPurpose(input.purpose);
598
+ const tokens = record(
599
+ await requestJson(
600
+ "https://github.com/login/oauth/access_token",
601
+ {
602
+ method: "POST",
603
+ headers: {
604
+ Accept: "application/json",
605
+ "Content-Type": "application/x-www-form-urlencoded"
606
+ },
607
+ body: new URLSearchParams({
608
+ client_id: config.clientId,
609
+ client_secret: config.clientSecret,
610
+ redirect_uri: config.redirectUri,
611
+ code: input.code,
612
+ code_verifier: input.verifier
613
+ }),
614
+ ...input.signal ? { signal: input.signal } : {}
615
+ },
616
+ fetcher
617
+ )
618
+ );
619
+ const accessToken = text(tokens.access_token);
620
+ const headers = {
621
+ Accept: "application/vnd.github+json",
622
+ Authorization: `Bearer ${accessToken}`,
623
+ "X-GitHub-Api-Version": "2026-03-10",
624
+ "User-Agent": "mycreat-labs-integrations-sdk"
625
+ };
626
+ const user = record(
627
+ await requestJson(
628
+ "https://api.github.com/user",
629
+ { headers, ...input.signal ? { signal: input.signal } : {} },
630
+ fetcher
631
+ )
632
+ );
633
+ if (!Number.isSafeInteger(user.id) || Number(user.id) <= 0)
634
+ throw new IntegrationError("invalid_identity");
635
+ const emails = await requestJson(
636
+ "https://api.github.com/user/emails?per_page=100",
637
+ { headers, ...input.signal ? { signal: input.signal } : {} },
638
+ fetcher
639
+ );
640
+ if (!Array.isArray(emails) || emails.length > 100)
641
+ throw new IntegrationError("invalid_identity");
642
+ const primary = emails.find(
643
+ (item) => item && typeof item === "object" && item.primary === true && item.verified === true
644
+ );
645
+ const email = primary ? text(primary.email, 320) : void 0;
646
+ const name = optionalText(user.name, 1024);
647
+ const username = text(user.login, 255);
648
+ return {
649
+ identity: {
650
+ provider: "github",
651
+ issuer: "https://github.com",
652
+ subject: String(user.id),
653
+ identityNamespace: config.identityNamespace ?? config.registrationId,
654
+ emailVerified: Boolean(email),
655
+ username,
656
+ ...email ? { email } : {},
657
+ ...name ? { name } : {}
658
+ }
659
+ };
660
+ }
661
+ }
662
+ };
663
+ }
664
+
665
+ // src/providers/notion.ts
666
+ var NOTION_API_VERSION = "2026-03-11";
667
+ function uuid2(value) {
668
+ if (!isUuid(value)) throw new IntegrationError("invalid_input");
669
+ return value;
670
+ }
671
+ var validCursor = (value) => typeof value === "string" && value.length > 0 && value.length <= 4096;
672
+ function validateNotionCreatePage(value) {
673
+ const input = inputRecord(value, [
674
+ "parentId",
675
+ "parentType",
676
+ "title",
677
+ "content",
678
+ "url",
679
+ "metadata"
680
+ ]);
681
+ const result = {
682
+ parentId: uuid2(input.parentId),
683
+ title: inputText(input.title, 2e3)
684
+ };
685
+ if (input.parentType !== void 0) {
686
+ if (input.parentType !== "page" && input.parentType !== "data_source")
687
+ throw new IntegrationError("invalid_input");
688
+ result.parentType = input.parentType;
689
+ }
690
+ if (input.content !== void 0)
691
+ result.content = inputText(input.content, 1e5, false);
692
+ if (input.url !== void 0) result.url = webUrl(input.url);
693
+ if (input.metadata !== void 0) {
694
+ const metadata = jsonMetadata(input.metadata);
695
+ if (!metadata || Array.isArray(metadata) || typeof metadata !== "object")
696
+ throw new IntegrationError("invalid_input");
697
+ result.metadata = metadata;
698
+ }
699
+ return result;
700
+ }
701
+ function richText(value) {
702
+ const result = [];
703
+ for (let index = 0; index < value.length; ) {
704
+ let end = Math.min(index + 2e3, value.length);
705
+ if (end < value.length && /[\uD800-\uDBFF]/.test(value.charAt(end - 1)))
706
+ end--;
707
+ result.push({ type: "text", text: { content: value.slice(index, end) } });
708
+ index = end;
709
+ }
710
+ return result;
711
+ }
712
+ function resourceTitle(value) {
713
+ if (!Array.isArray(value)) throw new IntegrationError("invalid_response");
714
+ return value.map((item) => {
715
+ const piece = record(item);
716
+ if (typeof piece.plain_text === "string") return piece.plain_text;
717
+ return text(record(piece.text).content, 2e3);
718
+ }).join("");
719
+ }
720
+ function pageResult(value) {
721
+ try {
722
+ const page = record(value);
723
+ if (page.object !== "page" || !isUuid(page.id)) throw new Error();
724
+ const url = text(page.url, 4096);
725
+ if (new URL(url).protocol !== "https:") throw new Error();
726
+ return { id: page.id, url };
727
+ } catch {
728
+ throw new IntegrationError("invalid_response", "outcome_unknown");
729
+ }
730
+ }
731
+ function createNotionClient(config) {
732
+ if (!config.accessToken)
733
+ throw new IntegrationError("auth_required", "requires_reauth");
734
+ const request = (path, options = {}) => notionRequest(path, { ...config, ...options });
735
+ async function listResources(input = {}) {
736
+ if (input.parentId !== void 0)
737
+ throw new IntegrationError("unsupported_resource_filter");
738
+ if (input.cursor !== void 0 && !validCursor(input.cursor))
739
+ throw new IntegrationError("invalid_input");
740
+ if (input.query !== void 0) inputText(input.query, 1e3, false);
741
+ const response = record(
742
+ await request("/search", {
743
+ body: {
744
+ page_size: 100,
745
+ ...input.cursor ? { start_cursor: input.cursor } : {},
746
+ ...input.query !== void 0 ? { query: input.query } : {}
747
+ }
748
+ })
749
+ );
750
+ if (!Array.isArray(response.results) || response.results.length > 100 || typeof response.has_more !== "boolean")
751
+ throw new IntegrationError("invalid_response");
752
+ if (response.request_status !== void 0 && record(response.request_status).type !== "complete")
753
+ throw new IntegrationError("resource_listing_incomplete");
754
+ if (response.has_more && !validCursor(response.next_cursor))
755
+ throw new IntegrationError("invalid_response");
756
+ const items = [];
757
+ for (const item of response.results) {
758
+ const entry = record(item);
759
+ if (!isUuid(entry.id) || entry.object !== "page" && entry.object !== "data_source")
760
+ throw new IntegrationError("invalid_response");
761
+ if (entry.in_trash === true || entry.archived === true || entry.is_archived === true)
762
+ continue;
763
+ let name;
764
+ if (entry.object === "data_source") name = resourceTitle(entry.title);
765
+ else {
766
+ const properties = record(entry.properties);
767
+ const title = Object.values(properties).map(record).find((property) => property.type === "title");
768
+ name = title ? resourceTitle(title.title) : "";
769
+ }
770
+ const url = optionalText(entry.url);
771
+ items.push({
772
+ id: entry.id,
773
+ type: entry.object,
774
+ name: name || "Untitled",
775
+ ...url ? { url } : {}
776
+ });
777
+ }
778
+ return {
779
+ items,
780
+ ...response.has_more ? { nextCursor: response.next_cursor } : {}
781
+ };
782
+ }
783
+ async function createPage(value) {
784
+ const input = validateNotionCreatePage(value);
785
+ const parentType = input.parentType ?? "page";
786
+ let titleKey = "title";
787
+ if (parentType === "data_source") {
788
+ const source = record(await request(`/data_sources/${input.parentId}`));
789
+ if (source.id !== input.parentId || source.object !== "data_source")
790
+ throw new IntegrationError("invalid_response");
791
+ const property = Object.entries(record(source.properties)).find(
792
+ ([, definition]) => record(definition).type === "title"
793
+ );
794
+ if (!property) throw new IntegrationError("invalid_resource");
795
+ titleKey = property[0];
796
+ }
797
+ const children = [];
798
+ if (input.content) {
799
+ for (const part of richText(input.content))
800
+ children.push({
801
+ object: "block",
802
+ type: "paragraph",
803
+ paragraph: { rich_text: [part] }
804
+ });
805
+ }
806
+ if (input.url)
807
+ children.push({
808
+ object: "block",
809
+ type: "bookmark",
810
+ bookmark: { url: input.url }
811
+ });
812
+ return pageResult(
813
+ await request("/pages", {
814
+ body: {
815
+ parent: parentType === "page" ? { page_id: input.parentId } : { data_source_id: input.parentId },
816
+ properties: {
817
+ [titleKey]: { type: "title", title: richText(input.title) }
818
+ },
819
+ ...children.length ? { children } : {}
820
+ }
821
+ })
822
+ );
823
+ }
824
+ return { request, listResources, resources: listResources, createPage };
825
+ }
826
+ function tokenCredentials(value, previous) {
827
+ try {
828
+ const response = record(value);
829
+ const accessToken = text(response.access_token, 8192);
830
+ const refreshToken = text(response.refresh_token, 8192);
831
+ if (typeof response.token_type !== "string" || response.token_type.toLowerCase() !== "bearer")
832
+ throw new Error();
833
+ const expires = response.expires_in;
834
+ if (expires !== void 0 && (typeof expires !== "number" || !Number.isInteger(expires) || expires <= 0 || expires > 31536e3))
835
+ throw new Error();
836
+ return {
837
+ accessToken,
838
+ refreshToken,
839
+ ...expires !== void 0 ? { expiresAt: Date.now() + Number(expires) * 1e3 } : {},
840
+ ...previous?.scopes ? { scopes: previous.scopes } : {},
841
+ ...previous?.extra ? { extra: previous.extra } : {}
842
+ };
843
+ } catch {
844
+ throw new IntegrationError("invalid_response", "outcome_unknown");
845
+ }
846
+ }
847
+ function createNotionProvider(config) {
848
+ if (!config.registrationId || !config.clientId || !config.clientSecret || !config.redirectUri)
849
+ throw new IntegrationError("invalid_provider_configuration");
850
+ const tokenRequest = (body, fetcher, signal) => notionRequest("/oauth/token", {
851
+ clientId: config.clientId,
852
+ clientSecret: config.clientSecret,
853
+ apiVersion: config.apiVersion,
854
+ body,
855
+ fetch: fetcher,
856
+ ...signal ? { signal } : {}
857
+ });
858
+ return {
859
+ key: "notion",
860
+ registrationId: config.registrationId,
861
+ purposes: ["connect"],
862
+ authorization: {
863
+ url(input) {
864
+ if (input.purpose !== "connect")
865
+ throw new IntegrationError("unsupported_purpose");
866
+ const url = new URL("https://api.notion.com/v1/oauth/authorize");
867
+ url.search = new URLSearchParams({
868
+ client_id: config.clientId,
869
+ redirect_uri: config.redirectUri,
870
+ response_type: "code",
871
+ owner: "user",
872
+ state: input.state
873
+ }).toString();
874
+ return url.toString();
875
+ },
876
+ async exchange(input, fetcher) {
877
+ if (input.purpose !== "connect")
878
+ throw new IntegrationError("unsupported_purpose");
879
+ const response = record(
880
+ await tokenRequest(
881
+ {
882
+ grant_type: "authorization_code",
883
+ code: input.code,
884
+ redirect_uri: config.redirectUri
885
+ },
886
+ fetcher,
887
+ input.signal
888
+ )
889
+ );
890
+ const credentials = tokenCredentials(response);
891
+ const workspaceId = text(response.workspace_id);
892
+ const botId = text(response.bot_id);
893
+ if (!isUuid(workspaceId) || !isUuid(botId))
894
+ throw new IntegrationError("invalid_response", "outcome_unknown");
895
+ const owner = record(response.owner);
896
+ if (owner.type !== "user")
897
+ throw new IntegrationError("invalid_response");
898
+ const userId = text(record(owner.user).id);
899
+ if (!isUuid(userId)) throw new IntegrationError("invalid_response");
900
+ const label = optionalText(response.workspace_name) ?? "Notion workspace";
901
+ credentials.extra = { workspaceId, botId, ownerId: userId };
902
+ return {
903
+ credentials,
904
+ account: {
905
+ id: `${workspaceId}:${botId}:${userId}`,
906
+ label,
907
+ metadata: { workspaceId, botId, ownerId: userId }
908
+ }
909
+ };
910
+ }
911
+ },
912
+ resources(input, context) {
913
+ return createNotionClient({
914
+ accessToken: context.credentials.accessToken,
915
+ fetch: context.fetch,
916
+ signal: context.signal,
917
+ apiVersion: config.apiVersion
918
+ }).listResources(input);
919
+ },
920
+ async refresh(credentials, context) {
921
+ if (!credentials.refreshToken)
922
+ throw new IntegrationError("auth_required", "requires_reauth");
923
+ return tokenCredentials(
924
+ await tokenRequest(
925
+ {
926
+ grant_type: "refresh_token",
927
+ refresh_token: credentials.refreshToken
928
+ },
929
+ context.fetch,
930
+ context.signal
931
+ ),
932
+ credentials
933
+ );
934
+ },
935
+ async revoke(credentials, context) {
936
+ await notionRequest("/oauth/revoke", {
937
+ clientId: config.clientId,
938
+ clientSecret: config.clientSecret,
939
+ apiVersion: config.apiVersion,
940
+ body: { token: credentials.accessToken },
941
+ fetch: context.fetch,
942
+ signal: context.signal
943
+ });
944
+ },
945
+ actions: {
946
+ create_page: {
947
+ validate(value) {
948
+ return validateNotionCreatePage(value);
949
+ },
950
+ async execute(value, context) {
951
+ const checkpoint = context.checkpoint && typeof context.checkpoint === "object" && !Array.isArray(context.checkpoint) ? context.checkpoint : {};
952
+ if (typeof checkpoint.id === "string" && typeof checkpoint.url === "string")
953
+ return { id: checkpoint.id, url: checkpoint.url };
954
+ const result = await createNotionClient({
955
+ accessToken: context.credentials.accessToken,
956
+ fetch: context.fetch,
957
+ signal: context.signal,
958
+ apiVersion: config.apiVersion
959
+ }).createPage(value);
960
+ await context.saveCheckpoint(result);
961
+ return result;
962
+ },
963
+ async reconcile(_value, context) {
964
+ const checkpoint = context.checkpoint && typeof context.checkpoint === "object" && !Array.isArray(context.checkpoint) ? context.checkpoint : {};
965
+ if (!isUuid(checkpoint.id)) return { resolved: false };
966
+ const result = pageResult(
967
+ await notionRequest(`/pages/${checkpoint.id}`, {
968
+ accessToken: context.credentials.accessToken,
969
+ fetch: context.fetch,
970
+ signal: context.signal,
971
+ apiVersion: config.apiVersion
972
+ })
973
+ );
974
+ if (result.id !== checkpoint.id)
975
+ throw new IntegrationError("invalid_response");
976
+ return { resolved: true, result };
977
+ }
978
+ }
979
+ }
980
+ };
981
+ }
982
+
983
+ // src/providers/raindrop.ts
984
+ var RAINDROP_TOKEN_ENDPOINT = "https://api.raindrop.io/v1/oauth/access_token";
985
+ function collectionId(value) {
986
+ if (!Number.isSafeInteger(value) || Number(value) !== -1 && Number(value) <= 0)
987
+ throw new IntegrationError("invalid_input");
988
+ return value;
989
+ }
990
+ function validateRaindropCreateBookmark(value) {
991
+ const input = inputRecord(value, [
992
+ "collectionId",
993
+ "url",
994
+ "title",
995
+ "note",
996
+ "excerpt",
997
+ "tags",
998
+ "metadata"
999
+ ]);
1000
+ const result = { url: webUrl(input.url) };
1001
+ if (input.collectionId !== void 0)
1002
+ result.collectionId = collectionId(input.collectionId);
1003
+ if (input.title !== void 0)
1004
+ result.title = inputText(input.title, 1e4, false);
1005
+ if (input.note !== void 0)
1006
+ result.note = inputText(input.note, 1e5, false);
1007
+ if (input.excerpt !== void 0)
1008
+ result.excerpt = inputText(input.excerpt, 1e4, false);
1009
+ if (input.tags !== void 0) {
1010
+ if (!Array.isArray(input.tags) || input.tags.length > 100 || input.tags.some(
1011
+ (tag) => typeof tag !== "string" || !tag.trim() || tag.length > 255
1012
+ ))
1013
+ throw new IntegrationError("invalid_input");
1014
+ result.tags = [...new Set(input.tags)];
1015
+ }
1016
+ if (input.metadata !== void 0) {
1017
+ const metadata = jsonMetadata(input.metadata);
1018
+ if (!metadata || Array.isArray(metadata) || typeof metadata !== "object")
1019
+ throw new IntegrationError("invalid_input");
1020
+ result.metadata = metadata;
1021
+ }
1022
+ return result;
1023
+ }
1024
+ function bookmarkResult(value, input) {
1025
+ try {
1026
+ const response = record(value);
1027
+ if (response.result !== true) throw new Error();
1028
+ const item = record(response.item);
1029
+ if (!Number.isSafeInteger(item._id) || Number(item._id) <= 0)
1030
+ throw new Error();
1031
+ const destination = collectionId(record(item.collection).$id);
1032
+ const link = text(item.link, 1e4);
1033
+ if (input && (new URL(link).toString() !== new URL(input.url).toString() || destination !== (input.collectionId ?? -1)))
1034
+ throw new Error();
1035
+ return {
1036
+ id: String(item._id),
1037
+ collectionId: destination,
1038
+ url: `https://app.raindrop.io/my/${destination}/item/${item._id}`
1039
+ };
1040
+ } catch {
1041
+ throw new IntegrationError("invalid_response", "outcome_unknown");
1042
+ }
1043
+ }
1044
+ function createRaindropClient(config) {
1045
+ if (!config.accessToken)
1046
+ throw new IntegrationError("auth_required", "requires_reauth");
1047
+ const request = (path, options = {}) => raindropRequest(path, { ...config, ...options });
1048
+ async function listCollections(input = {}) {
1049
+ if (input.cursor !== void 0) throw new IntegrationError("invalid_input");
1050
+ if (input.query !== void 0) inputText(input.query, 1e3, false);
1051
+ if (input.parentId !== void 0 && !/^[1-9][0-9]*$/.test(input.parentId))
1052
+ throw new IntegrationError("invalid_input");
1053
+ const pages = await Promise.all([
1054
+ request("/collections"),
1055
+ request("/collections/childrens")
1056
+ ]);
1057
+ const resources = /* @__PURE__ */ new Map();
1058
+ resources.set("-1", { id: "-1", type: "collection", name: "Unsorted" });
1059
+ for (const value of pages) {
1060
+ const response = record(value);
1061
+ if (response.result !== true || !Array.isArray(response.items) || response.items.length > 1e4)
1062
+ throw new IntegrationError("invalid_response");
1063
+ for (const value2 of response.items) {
1064
+ const item = record(value2);
1065
+ if (!Number.isSafeInteger(item._id) || Number(item._id) <= 0)
1066
+ throw new IntegrationError("invalid_response");
1067
+ const level = record(item.access).level;
1068
+ if (!Number.isInteger(level) || Number(level) < 1 || Number(level) > 4)
1069
+ throw new IntegrationError("invalid_response");
1070
+ if (Number(level) < 3) continue;
1071
+ let parentId;
1072
+ if (item.parent !== void 0 && item.parent !== null) {
1073
+ const parent = record(item.parent);
1074
+ const id = parent.$id ?? parent._id;
1075
+ if (!Number.isSafeInteger(id) || Number(id) <= 0)
1076
+ throw new IntegrationError("invalid_response");
1077
+ parentId = String(id);
1078
+ }
1079
+ resources.set(String(item._id), {
1080
+ id: String(item._id),
1081
+ type: "collection",
1082
+ name: text(item.title),
1083
+ metadata: {
1084
+ accessLevel: Number(level),
1085
+ ...parentId ? { parentId } : {}
1086
+ }
1087
+ });
1088
+ }
1089
+ }
1090
+ return {
1091
+ items: [...resources.values()].filter(
1092
+ (item) => (!input.parentId || item.metadata?.parentId === input.parentId) && (!input.query || item.name.toLocaleLowerCase().includes(input.query.toLocaleLowerCase()))
1093
+ )
1094
+ };
1095
+ }
1096
+ async function createBookmark(value) {
1097
+ const input = validateRaindropCreateBookmark(value);
1098
+ const id = input.collectionId ?? -1;
1099
+ if (id !== -1) {
1100
+ const response = record(await request(`/collection/${id}`));
1101
+ const item = record(response.item);
1102
+ if (response.result !== true || item._id !== id)
1103
+ throw new IntegrationError("invalid_response");
1104
+ const level = record(item.access).level;
1105
+ if (level !== 3 && level !== 4)
1106
+ throw new IntegrationError("permission_required");
1107
+ }
1108
+ const payload = {
1109
+ link: input.url,
1110
+ collection: { $id: id },
1111
+ ...input.title !== void 0 ? { title: input.title } : {},
1112
+ ...input.note !== void 0 ? { note: input.note } : {},
1113
+ ...input.excerpt !== void 0 ? { excerpt: input.excerpt } : {},
1114
+ ...input.tags ? { tags: input.tags } : {}
1115
+ };
1116
+ return bookmarkResult(await request("/raindrop", { body: payload }), input);
1117
+ }
1118
+ return {
1119
+ request,
1120
+ listCollections,
1121
+ resources: listCollections,
1122
+ createBookmark
1123
+ };
1124
+ }
1125
+ function tokenCredentials2(value, previous) {
1126
+ try {
1127
+ const response = record(value);
1128
+ const accessToken = text(response.access_token, 8192);
1129
+ const refreshToken = text(response.refresh_token, 8192);
1130
+ const expires = response.expires_in;
1131
+ if (typeof response.token_type !== "string" || response.token_type.toLowerCase() !== "bearer" || typeof expires !== "number" || !Number.isInteger(expires) || expires <= 0 || expires > 31536e3)
1132
+ throw new Error();
1133
+ return {
1134
+ accessToken,
1135
+ refreshToken,
1136
+ expiresAt: Date.now() + expires * 1e3,
1137
+ ...previous?.extra ? { extra: previous.extra } : {}
1138
+ };
1139
+ } catch {
1140
+ throw new IntegrationError("invalid_response", "outcome_unknown");
1141
+ }
1142
+ }
1143
+ function createRaindropProvider(config) {
1144
+ if (!config.registrationId || !config.clientId || !config.clientSecret || !config.redirectUri)
1145
+ throw new IntegrationError("invalid_provider_configuration");
1146
+ const tokenRequest = (body, fetcher, signal) => raindropRequest("/v1/oauth/access_token", {
1147
+ body: {
1148
+ client_id: config.clientId,
1149
+ client_secret: config.clientSecret,
1150
+ ...body
1151
+ },
1152
+ fetch: fetcher,
1153
+ ...signal ? { signal } : {}
1154
+ });
1155
+ return {
1156
+ key: "raindrop",
1157
+ registrationId: config.registrationId,
1158
+ purposes: ["connect"],
1159
+ authorization: {
1160
+ url(input) {
1161
+ if (input.purpose !== "connect")
1162
+ throw new IntegrationError("unsupported_purpose");
1163
+ const url = new URL("https://raindrop.io/oauth/authorize");
1164
+ url.search = new URLSearchParams({
1165
+ client_id: config.clientId,
1166
+ redirect_uri: config.redirectUri,
1167
+ response_type: "code",
1168
+ state: input.state
1169
+ }).toString();
1170
+ return url.toString();
1171
+ },
1172
+ async exchange(input, fetcher) {
1173
+ if (input.purpose !== "connect")
1174
+ throw new IntegrationError("unsupported_purpose");
1175
+ const credentials = tokenCredentials2(
1176
+ await tokenRequest(
1177
+ {
1178
+ grant_type: "authorization_code",
1179
+ code: input.code,
1180
+ redirect_uri: config.redirectUri
1181
+ },
1182
+ fetcher,
1183
+ input.signal
1184
+ )
1185
+ );
1186
+ const response = record(
1187
+ await raindropRequest("/user", {
1188
+ accessToken: credentials.accessToken,
1189
+ fetch: fetcher,
1190
+ signal: input.signal
1191
+ })
1192
+ );
1193
+ const user = record(response.user);
1194
+ if (response.result !== true || !Number.isSafeInteger(user._id) || Number(user._id) <= 0)
1195
+ throw new IntegrationError("invalid_response");
1196
+ const id = String(user._id);
1197
+ return {
1198
+ credentials,
1199
+ account: {
1200
+ id,
1201
+ label: optionalText(user.fullName) ?? `Raindrop ${id}`
1202
+ }
1203
+ };
1204
+ }
1205
+ },
1206
+ resources(input, context) {
1207
+ return createRaindropClient({
1208
+ accessToken: context.credentials.accessToken,
1209
+ fetch: context.fetch,
1210
+ signal: context.signal
1211
+ }).listCollections(input);
1212
+ },
1213
+ async refresh(credentials, context) {
1214
+ if (!credentials.refreshToken)
1215
+ throw new IntegrationError("auth_required", "requires_reauth");
1216
+ return tokenCredentials2(
1217
+ await tokenRequest(
1218
+ {
1219
+ grant_type: "refresh_token",
1220
+ refresh_token: credentials.refreshToken
1221
+ },
1222
+ context.fetch,
1223
+ context.signal
1224
+ ),
1225
+ credentials
1226
+ );
1227
+ },
1228
+ // Raindrop does not document a revocation API; local disconnect must not pretend to revoke the upstream grant.
1229
+ actions: {
1230
+ create_bookmark: {
1231
+ validate(value) {
1232
+ return validateRaindropCreateBookmark(value);
1233
+ },
1234
+ async execute(value, context) {
1235
+ const checkpoint = context.checkpoint && typeof context.checkpoint === "object" && !Array.isArray(context.checkpoint) ? context.checkpoint : {};
1236
+ if (typeof checkpoint.id === "string" && typeof checkpoint.url === "string" && typeof checkpoint.collectionId === "number")
1237
+ return {
1238
+ id: checkpoint.id,
1239
+ url: checkpoint.url,
1240
+ collectionId: checkpoint.collectionId
1241
+ };
1242
+ const result = await createRaindropClient({
1243
+ accessToken: context.credentials.accessToken,
1244
+ fetch: context.fetch,
1245
+ signal: context.signal
1246
+ }).createBookmark(value);
1247
+ await context.saveCheckpoint(result);
1248
+ return result;
1249
+ },
1250
+ async reconcile(value, context) {
1251
+ const checkpoint = context.checkpoint && typeof context.checkpoint === "object" && !Array.isArray(context.checkpoint) ? context.checkpoint : {};
1252
+ if (typeof checkpoint.id !== "string" || !/^[1-9][0-9]*$/.test(checkpoint.id))
1253
+ return { resolved: false };
1254
+ const result = bookmarkResult(
1255
+ await raindropRequest(`/raindrop/${checkpoint.id}`, {
1256
+ accessToken: context.credentials.accessToken,
1257
+ fetch: context.fetch,
1258
+ signal: context.signal
1259
+ }),
1260
+ validateRaindropCreateBookmark(value)
1261
+ );
1262
+ if (result.id !== checkpoint.id)
1263
+ throw new IntegrationError("invalid_response");
1264
+ return { resolved: true, result };
1265
+ }
1266
+ }
1267
+ }
1268
+ };
1269
+ }
1270
+ // Annotate the CommonJS export names for ESM import in node:
1271
+ 0 && (module.exports = {
1272
+ NOTION_API_VERSION,
1273
+ ProviderHttpError,
1274
+ RAINDROP_TOKEN_ENDPOINT,
1275
+ createAppleProvider,
1276
+ createGitHubProvider,
1277
+ createGoogleProvider,
1278
+ createNotionClient,
1279
+ createNotionProvider,
1280
+ createRaindropClient,
1281
+ createRaindropProvider,
1282
+ notionRequest,
1283
+ raindropRequest,
1284
+ validateNotionCreatePage,
1285
+ validateRaindropCreateBookmark
1286
+ });
1287
+ //# sourceMappingURL=index.cjs.map