mcp-scraper 0.43.4 → 0.43.5

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 (49) hide show
  1. package/README.md +1 -1
  2. package/dist/bin/api-server.cjs +79 -25
  3. package/dist/bin/api-server.cjs.map +1 -1
  4. package/dist/bin/api-server.js +3 -3
  5. package/dist/bin/mcp-scraper-cli.cjs +1 -1
  6. package/dist/bin/mcp-scraper-cli.cjs.map +1 -1
  7. package/dist/bin/mcp-scraper-cli.js +1 -1
  8. package/dist/bin/mcp-scraper-install.cjs +1 -1
  9. package/dist/bin/mcp-scraper-install.cjs.map +1 -1
  10. package/dist/bin/mcp-scraper-install.js +1 -1
  11. package/dist/bin/mcp-stdio-server.cjs +1 -1
  12. package/dist/bin/mcp-stdio-server.cjs.map +1 -1
  13. package/dist/bin/mcp-stdio-server.js +4 -4
  14. package/dist/bin/paa-harvest.cjs.map +1 -1
  15. package/dist/bin/paa-harvest.js +3 -3
  16. package/dist/{chunk-FCFQ634B.js → chunk-5X7CJEK3.js} +25 -8
  17. package/dist/chunk-5X7CJEK3.js.map +1 -0
  18. package/dist/chunk-H4RPUFRR.js +7 -0
  19. package/dist/chunk-H4RPUFRR.js.map +1 -0
  20. package/dist/{chunk-ZBQ6EXZW.js → chunk-OVD4E4AP.js} +2 -2
  21. package/dist/{chunk-6TAQ2MXK.js → chunk-RJMOOII7.js} +2 -2
  22. package/dist/{chunk-SXLQZKWC.js → chunk-RSGUS5V6.js} +3 -3
  23. package/dist/{chunk-ZDVQARDQ.js → chunk-T5AFM4G5.js} +2 -2
  24. package/dist/{chunk-QOWBJ4YY.js → chunk-WT2UHDSE.js} +2 -2
  25. package/dist/{chunk-GUI33Q27.js → chunk-WWIJ2NID.js} +2 -2
  26. package/dist/{db-C5ELI55I.js → db-4ABEYNDM.js} +4 -2
  27. package/dist/{extract-bundle-IOM45SFL.js → extract-bundle-ITPFWRNI.js} +3 -3
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.js +3 -3
  30. package/dist/{location-data-repository-SHEOIBTL.js → location-data-repository-HDNCU4Z4.js} +3 -3
  31. package/dist/{server-K76DL4S7.js → server-BTDXZA5U.js} +68 -31
  32. package/dist/{server-K76DL4S7.js.map → server-BTDXZA5U.js.map} +1 -1
  33. package/dist/{site-extract-repository-XNI4ZK2M.js → site-extract-repository-CKCJDP7N.js} +3 -3
  34. package/dist/{worker-OB6WSWOD.js → worker-73AYU4IK.js} +5 -5
  35. package/package.json +2 -1
  36. package/dist/chunk-FCFQ634B.js.map +0 -1
  37. package/dist/chunk-XQIVXIAL.js +0 -7
  38. package/dist/chunk-XQIVXIAL.js.map +0 -1
  39. /package/dist/{chunk-ZBQ6EXZW.js.map → chunk-OVD4E4AP.js.map} +0 -0
  40. /package/dist/{chunk-6TAQ2MXK.js.map → chunk-RJMOOII7.js.map} +0 -0
  41. /package/dist/{chunk-SXLQZKWC.js.map → chunk-RSGUS5V6.js.map} +0 -0
  42. /package/dist/{chunk-ZDVQARDQ.js.map → chunk-T5AFM4G5.js.map} +0 -0
  43. /package/dist/{chunk-QOWBJ4YY.js.map → chunk-WT2UHDSE.js.map} +0 -0
  44. /package/dist/{chunk-GUI33Q27.js.map → chunk-WWIJ2NID.js.map} +0 -0
  45. /package/dist/{db-C5ELI55I.js.map → db-4ABEYNDM.js.map} +0 -0
  46. /package/dist/{extract-bundle-IOM45SFL.js.map → extract-bundle-ITPFWRNI.js.map} +0 -0
  47. /package/dist/{location-data-repository-SHEOIBTL.js.map → location-data-repository-HDNCU4Z4.js.map} +0 -0
  48. /package/dist/{site-extract-repository-XNI4ZK2M.js.map → site-extract-repository-CKCJDP7N.js.map} +0 -0
  49. /package/dist/{worker-OB6WSWOD.js.map → worker-73AYU4IK.js.map} +0 -0
package/README.md CHANGED
@@ -90,7 +90,7 @@ Build the branded one-click bundle:
90
90
  npm run build:mcpb
91
91
  ```
92
92
 
93
- The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.43.4`, SHA-256 `f6181c189de21204e9c22c6b1e4cff322ee021ab6708c5271f02f8cc6b5f005f`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
93
+ The generated bundle is written to `build/mcpb/mcp-scraper-<version>.mcpb` and copied to `public/downloads/` for the hosted download. The current public bundle is `https://mcpscraper.dev/downloads/mcp-scraper.mcpb` (`0.43.5`, SHA-256 `2c70cc4490f44d78f6803ab2e2b2e2fcdde72949532be053192744815bebe2aa`). Install it by opening or dragging it into Claude Desktop. Claude displays the `MCP Scraper` install card, icon, and API-key configuration field from the bundle manifest.
94
94
 
95
95
  The MCPB install exposes every tool — web-intelligence plus all `browser_*` tools — through the one `mcp-scraper` server.
96
96
 
@@ -3671,6 +3671,7 @@ __export(db_exports, {
3671
3671
  CORE_SCHEMA_VERSION: () => CORE_SCHEMA_VERSION,
3672
3672
  CREDIT_LOTS_EPOCH: () => CREDIT_LOTS_EPOCH,
3673
3673
  CREDIT_LOT_TTL: () => CREDIT_LOT_TTL,
3674
+ SITE_EXTRACT_COUNTER_REPAIR_VERSION: () => SITE_EXTRACT_COUNTER_REPAIR_VERSION,
3674
3675
  SiteAuditJobRowSchema: () => SiteAuditJobRowSchema,
3675
3676
  SiteAuditPhaseLogRowSchema: () => SiteAuditPhaseLogRowSchema,
3676
3677
  advanceWorkflowRunStep: () => advanceWorkflowRunStep,
@@ -3808,7 +3809,7 @@ async function hasCurrentCoreSchema(db) {
3808
3809
  const result = await db.execute(`SELECT ${checks.join(" AND ")} AS ready`);
3809
3810
  return Number(result.rows[0]?.ready ?? 0) === 1;
3810
3811
  }
3811
- async function runIdempotentCoreDataBackfills(db) {
3812
+ async function runSiteExtractCounterRepair(db) {
3812
3813
  await db.execute(`
3813
3814
  UPDATE site_extract_jobs SET
3814
3815
  attempted_urls = done_urls,
@@ -3848,6 +3849,18 @@ async function runIdempotentCoreDataBackfills(db) {
3848
3849
  AND failed_urls = 0
3849
3850
  `);
3850
3851
  }
3852
+ async function runPendingDataRepairs(db) {
3853
+ const applied = await db.execute({
3854
+ sql: "SELECT 1 AS applied FROM schema_migrations WHERE version = ? LIMIT 1",
3855
+ args: [SITE_EXTRACT_COUNTER_REPAIR_VERSION]
3856
+ });
3857
+ if (applied.rows.length > 0) return;
3858
+ await runSiteExtractCounterRepair(db);
3859
+ await db.execute({
3860
+ sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
3861
+ args: [SITE_EXTRACT_COUNTER_REPAIR_VERSION]
3862
+ });
3863
+ }
3851
3864
  function getDb() {
3852
3865
  if (!_db) _db = (0, import_http.createClient)({ url: DB_URL, authToken: DB_TOKEN });
3853
3866
  return _db;
@@ -3855,7 +3868,7 @@ function getDb() {
3855
3868
  function setDbForTesting(client2) {
3856
3869
  _db = client2;
3857
3870
  }
3858
- async function migrate() {
3871
+ async function migrate(options = {}) {
3859
3872
  const db = getDb();
3860
3873
  let applied;
3861
3874
  try {
@@ -3874,12 +3887,15 @@ async function migrate() {
3874
3887
  `);
3875
3888
  applied = { rows: [] };
3876
3889
  }
3877
- if (applied.rows.length > 0 && await hasCurrentCoreSchema(db)) {
3878
- await runIdempotentCoreDataBackfills(db);
3879
- return;
3890
+ if (applied.rows.length > 0) {
3891
+ if (!options.verifyCurrentSchema) return;
3892
+ if (await hasCurrentCoreSchema(db)) {
3893
+ if (options.runDataRepairs) await runPendingDataRepairs(db);
3894
+ return;
3895
+ }
3880
3896
  }
3881
3897
  if (await hasCurrentCoreSchema(db)) {
3882
- await runIdempotentCoreDataBackfills(db);
3898
+ await runPendingDataRepairs(db);
3883
3899
  await db.execute({
3884
3900
  sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
3885
3901
  args: [CORE_SCHEMA_VERSION]
@@ -4692,7 +4708,7 @@ async function migrate() {
4692
4708
  if (!await hasCurrentCoreSchema(db)) {
4693
4709
  throw new Error("core schema migration finished without all required tables, indexes, and columns");
4694
4710
  }
4695
- await runIdempotentCoreDataBackfills(db);
4711
+ await runPendingDataRepairs(db);
4696
4712
  await db.execute({
4697
4713
  sql: "INSERT OR IGNORE INTO schema_migrations (version) VALUES (?)",
4698
4714
  args: [CORE_SCHEMA_VERSION]
@@ -6666,7 +6682,7 @@ async function listActiveUserIds() {
6666
6682
  const res = await getDb().execute("SELECT id FROM users WHERE active = 1");
6667
6683
  return res.rows.map((r) => Number(r.id));
6668
6684
  }
6669
- var import_http, import_node_crypto, import_zod, DB_URL, DB_TOKEN, _db, _rateLimitSchemaReady, CORE_SCHEMA_VERSION, CORE_SCHEMA_TABLES, CORE_SCHEMA_INDEXES, CORE_SCHEMA_TRIGGERS, CORE_SCHEMA_COLUMNS, REQUEST_EVENT_RESULT_MAX_BYTES, CREDIT_LOT_TTL, SiteAuditJobRowSchema, SiteAuditPhaseLogRowSchema;
6685
+ var import_http, import_node_crypto, import_zod, DB_URL, DB_TOKEN, _db, _rateLimitSchemaReady, CORE_SCHEMA_VERSION, SITE_EXTRACT_COUNTER_REPAIR_VERSION, CORE_SCHEMA_TABLES, CORE_SCHEMA_INDEXES, CORE_SCHEMA_TRIGGERS, CORE_SCHEMA_COLUMNS, REQUEST_EVENT_RESULT_MAX_BYTES, CREDIT_LOT_TTL, SiteAuditJobRowSchema, SiteAuditPhaseLogRowSchema;
6670
6686
  var init_db = __esm({
6671
6687
  "src/api/db.ts"() {
6672
6688
  "use strict";
@@ -6680,6 +6696,7 @@ var init_db = __esm({
6680
6696
  _db = null;
6681
6697
  _rateLimitSchemaReady = false;
6682
6698
  CORE_SCHEMA_VERSION = "2026-07-30.1";
6699
+ SITE_EXTRACT_COUNTER_REPAIR_VERSION = "data-2026-07-28-site-extract-counters.1";
6683
6700
  CORE_SCHEMA_TABLES = [
6684
6701
  "admin_credit_adjustment_events",
6685
6702
  "admin_credit_adjustments",
@@ -39517,7 +39534,7 @@ var PACKAGE_VERSION;
39517
39534
  var init_version = __esm({
39518
39535
  "src/version.ts"() {
39519
39536
  "use strict";
39520
- PACKAGE_VERSION = "0.43.4";
39537
+ PACKAGE_VERSION = "0.43.5";
39521
39538
  }
39522
39539
  });
39523
39540
 
@@ -52096,6 +52113,22 @@ function isScraperResource(resource) {
52096
52113
  if (!resource) return false;
52097
52114
  return resource.replace(/\/$/, "") === SCRAPER_RESOURCE();
52098
52115
  }
52116
+ async function withOAuthDatabaseDeadline(stage, operation, timeoutMs = OAUTH_DATABASE_TIMEOUT_MS) {
52117
+ const startedAt = Date.now();
52118
+ let timeout;
52119
+ try {
52120
+ const value = await Promise.race([
52121
+ operation,
52122
+ new Promise((_, reject) => {
52123
+ timeout = setTimeout(() => reject(new OAuthDatabaseUnavailableError(stage)), timeoutMs);
52124
+ })
52125
+ ]);
52126
+ console.log("[oauth-db] stage=%s status=ok duration_ms=%d", stage, Date.now() - startedAt);
52127
+ return value;
52128
+ } finally {
52129
+ if (timeout) clearTimeout(timeout);
52130
+ }
52131
+ }
52099
52132
  async function getKeys() {
52100
52133
  if (cachedKeys) return cachedKeys;
52101
52134
  const raw = process.env.OAUTH_SIGNING_KEY;
@@ -52149,7 +52182,7 @@ async function sessionUser(token4) {
52149
52182
  if (!token4) return null;
52150
52183
  const userId = verifySession(token4);
52151
52184
  if (!userId) return null;
52152
- const user = await getUserById(userId);
52185
+ const user = await withOAuthDatabaseDeadline("session-user", getUserById(userId));
52153
52186
  return user ?? null;
52154
52187
  }
52155
52188
  function escapeHtml2(value) {
@@ -52274,7 +52307,7 @@ function parseAuthParams(get) {
52274
52307
  async function validateAuthRequest(p) {
52275
52308
  if (p.response_type !== "code") return { ok: false, status: 400, message: "response_type must be code" };
52276
52309
  if (!p.client_id) return { ok: false, status: 400, message: "client_id is required" };
52277
- const client2 = await getClient(p.client_id);
52310
+ const client2 = await withOAuthDatabaseDeadline("get-client", getClient(p.client_id));
52278
52311
  if (!client2) return { ok: false, status: 400, message: "unknown client_id" };
52279
52312
  if (!p.redirect_uri || !client2.redirect_uris.includes(p.redirect_uri)) return { ok: false, status: 400, message: "redirect_uri does not match the registered set" };
52280
52313
  if (!isAllowedRedirect(p.redirect_uri)) return { ok: false, status: 400, message: "redirect_uri host not allowed" };
@@ -52293,7 +52326,7 @@ async function mintAccessToken(identity, scope, plan, audience) {
52293
52326
  function tokenErrorResponse(c, error, description, status) {
52294
52327
  return c.json({ error, error_description: description }, status);
52295
52328
  }
52296
- var import_hono23, import_cookie, import_node_crypto28, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
52329
+ var import_hono23, import_cookie, import_node_crypto28, import_jose2, ISSUER, RESOURCE, SCRAPER_RESOURCE, MEMORY_SCOPES, SCRAPER_SCOPES, SUPPORTED_SCOPES, ACCESS_TTL_SECONDS, REFRESH_TTL_SECONDS, CODE_TTL_SECONDS, ROTATION_GRACE_SECONDS, OAUTH_DATABASE_TIMEOUT_MS, OAuthDatabaseUnavailableError, secureCookies, sessionCookieOptions, cachedKeys, oauthApp;
52297
52330
  var init_oauth_routes = __esm({
52298
52331
  "src/api/oauth-routes.ts"() {
52299
52332
  "use strict";
@@ -52315,6 +52348,15 @@ var init_oauth_routes = __esm({
52315
52348
  REFRESH_TTL_SECONDS = 60 * 60 * 24 * 30;
52316
52349
  CODE_TTL_SECONDS = 60;
52317
52350
  ROTATION_GRACE_SECONDS = 120;
52351
+ OAUTH_DATABASE_TIMEOUT_MS = 5e3;
52352
+ OAuthDatabaseUnavailableError = class extends Error {
52353
+ stage;
52354
+ constructor(stage) {
52355
+ super("The authorization service database is temporarily unavailable.");
52356
+ this.name = "OAuthDatabaseUnavailableError";
52357
+ this.stage = stage;
52358
+ }
52359
+ };
52318
52360
  secureCookies = process.env.NODE_ENV === "production" || process.env.VERCEL === "1";
52319
52361
  sessionCookieOptions = {
52320
52362
  httpOnly: true,
@@ -52325,6 +52367,18 @@ var init_oauth_routes = __esm({
52325
52367
  };
52326
52368
  cachedKeys = null;
52327
52369
  oauthApp = new import_hono23.Hono();
52370
+ oauthApp.onError((error, c) => {
52371
+ if (error instanceof OAuthDatabaseUnavailableError) {
52372
+ console.error("[oauth-db] stage=%s status=timeout timeout_ms=%d", error.stage, OAUTH_DATABASE_TIMEOUT_MS);
52373
+ c.header("Retry-After", "5");
52374
+ return c.json({
52375
+ error: "temporarily_unavailable",
52376
+ error_description: "Authorization is temporarily unavailable. Please retry shortly."
52377
+ }, 503);
52378
+ }
52379
+ console.error("[oauth] unhandled error:", error);
52380
+ return c.json({ error: "server_error", error_description: "Authorization could not be completed." }, 500);
52381
+ });
52328
52382
  oauthApp.use("*", async (c, next) => {
52329
52383
  c.header("Access-Control-Allow-Origin", "*");
52330
52384
  c.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
@@ -52386,7 +52440,7 @@ var init_oauth_routes = __esm({
52386
52440
  }
52387
52441
  const clientName = typeof body.client_name === "string" ? body.client_name : null;
52388
52442
  const clientId = `client_${(0, import_node_crypto28.randomBytes)(16).toString("hex")}`;
52389
- await registerClient(clientId, redirectUris, clientName);
52443
+ await withOAuthDatabaseDeadline("register-client", registerClient(clientId, redirectUris, clientName));
52390
52444
  console.log("[oauth-dcr] register OK client_id=%s redirect_uris=%s", clientId, JSON.stringify(redirectUris));
52391
52445
  return c.json({
52392
52446
  client_id: clientId,
@@ -52424,7 +52478,7 @@ var init_oauth_routes = __esm({
52424
52478
  if (action === "login") {
52425
52479
  const email = (get("email") ?? "").trim().toLowerCase();
52426
52480
  const password = get("password") ?? "";
52427
- const user2 = email ? await getUserByEmail(email) : void 0;
52481
+ const user2 = email ? await withOAuthDatabaseDeadline("login-user", getUserByEmail(email)) : void 0;
52428
52482
  const okPw = !!(user2 && user2.password_hash && verifyPassword(password, user2.password_hash));
52429
52483
  console.log("[oauth-dcr] LOGIN email=%s found=%s hasHash=%s pwLen=%d ok=%s", email, !!user2, !!(user2 && user2.password_hash), password.length, okPw);
52430
52484
  if (!okPw) {
@@ -52440,7 +52494,7 @@ var init_oauth_routes = __esm({
52440
52494
  const scope = negotiateScope(p.scope, user, p.resource);
52441
52495
  const code = `code_${(0, import_node_crypto28.randomBytes)(32).toString("base64url")}`;
52442
52496
  const expiresAt = new Date(Date.now() + CODE_TTL_SECONDS * 1e3).toISOString();
52443
- await putCode({
52497
+ await withOAuthDatabaseDeadline("put-authorization-code", putCode({
52444
52498
  code,
52445
52499
  client_id: p.client_id,
52446
52500
  redirect_uri: p.redirect_uri,
@@ -52449,7 +52503,7 @@ var init_oauth_routes = __esm({
52449
52503
  scope,
52450
52504
  resource: p.resource || RESOURCE(),
52451
52505
  expires_at: expiresAt
52452
- });
52506
+ }));
52453
52507
  const u = new URL(p.redirect_uri);
52454
52508
  u.searchParams.set("code", code);
52455
52509
  if (p.state) u.searchParams.set("state", p.state);
@@ -52467,18 +52521,18 @@ var init_oauth_routes = __esm({
52467
52521
  const clientId = get("client_id") ?? "";
52468
52522
  const redirectUri = get("redirect_uri") ?? "";
52469
52523
  const verifier = get("code_verifier") ?? "";
52470
- const record = await consumeCode(code);
52524
+ const record = await withOAuthDatabaseDeadline("consume-authorization-code", consumeCode(code));
52471
52525
  if (!record) return tokenErrorResponse(c, "invalid_grant", "Authorization code is invalid or already used", 400);
52472
52526
  if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Authorization code has expired", 400);
52473
52527
  if (record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
52474
52528
  if (record.redirect_uri !== redirectUri) return tokenErrorResponse(c, "invalid_grant", "redirect_uri mismatch", 400);
52475
52529
  if (!pkceMatches(verifier, record.code_challenge)) return tokenErrorResponse(c, "invalid_grant", "PKCE verification failed", 400);
52476
- const user = await getUserByEmail(record.identity);
52530
+ const user = await withOAuthDatabaseDeadline("token-user", getUserByEmail(record.identity));
52477
52531
  const plan = user ? resolvePlan(user) : "free";
52478
52532
  const audience = record.resource ?? RESOURCE();
52479
52533
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
52480
52534
  const refreshToken = `rt_${(0, import_node_crypto28.randomBytes)(40).toString("base64url")}`;
52481
- await putRefresh({
52535
+ await withOAuthDatabaseDeadline("put-refresh-token", putRefresh({
52482
52536
  refresh_token: refreshToken,
52483
52537
  client_id: clientId,
52484
52538
  identity: record.identity,
@@ -52486,15 +52540,15 @@ var init_oauth_routes = __esm({
52486
52540
  resource: audience,
52487
52541
  expires_at: new Date(Date.now() + REFRESH_TTL_SECONDS * 1e3).toISOString(),
52488
52542
  revoked: 0
52489
- });
52543
+ }));
52490
52544
  return c.json({ access_token: accessToken, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: refreshToken, scope: record.scope });
52491
52545
  }
52492
52546
  if (grantType === "refresh_token") {
52493
52547
  const refreshToken = get("refresh_token") ?? "";
52494
52548
  const clientId = get("client_id") ?? "";
52495
- const record = await getRefresh(refreshToken);
52549
+ const record = await withOAuthDatabaseDeadline("get-refresh-token", getRefresh(refreshToken));
52496
52550
  if (record && record.revoked && record.replaced_by && record.rotated_at && Date.now() - new Date(record.rotated_at).getTime() < ROTATION_GRACE_SECONDS * 1e3 && (!clientId || record.client_id === clientId)) {
52497
- const graceUser = await getUserByEmail(record.identity);
52551
+ const graceUser = await withOAuthDatabaseDeadline("refresh-grace-user", getUserByEmail(record.identity));
52498
52552
  const gracePlan = graceUser ? resolvePlan(graceUser) : "free";
52499
52553
  const graceAudience = record.resource ?? RESOURCE();
52500
52554
  const graceAccess = await mintAccessToken(record.identity, record.scope, gracePlan, graceAudience);
@@ -52503,12 +52557,12 @@ var init_oauth_routes = __esm({
52503
52557
  if (!record || record.revoked) return tokenErrorResponse(c, "invalid_grant", "Refresh token is invalid or revoked", 400);
52504
52558
  if (new Date(record.expires_at).getTime() < Date.now()) return tokenErrorResponse(c, "invalid_grant", "Refresh token has expired", 400);
52505
52559
  if (clientId && record.client_id !== clientId) return tokenErrorResponse(c, "invalid_grant", "client_id mismatch", 400);
52506
- const user = await getUserByEmail(record.identity);
52560
+ const user = await withOAuthDatabaseDeadline("refresh-user", getUserByEmail(record.identity));
52507
52561
  const plan = user ? resolvePlan(user) : "free";
52508
52562
  const audience = record.resource ?? RESOURCE();
52509
52563
  const accessToken = await mintAccessToken(record.identity, record.scope, plan, audience);
52510
52564
  const nextRefresh = `rt_${(0, import_node_crypto28.randomBytes)(40).toString("base64url")}`;
52511
- await rotateRefresh(refreshToken, {
52565
+ await withOAuthDatabaseDeadline("rotate-refresh-token", rotateRefresh(refreshToken, {
52512
52566
  refresh_token: nextRefresh,
52513
52567
  client_id: record.client_id,
52514
52568
  identity: record.identity,
@@ -52516,7 +52570,7 @@ var init_oauth_routes = __esm({
52516
52570
  resource: audience,
52517
52571
  expires_at: new Date(Date.now() + REFRESH_TTL_SECONDS * 1e3).toISOString(),
52518
52572
  revoked: 0
52519
- });
52573
+ }));
52520
52574
  return c.json({ access_token: accessToken, token_type: "Bearer", expires_in: ACCESS_TTL_SECONDS, refresh_token: nextRefresh, scope: record.scope });
52521
52575
  }
52522
52576
  return tokenErrorResponse(c, "unsupported_grant_type", "grant_type must be authorization_code or refresh_token", 400);