@rebasepro/server-core 0.2.1 → 0.2.4

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 (113) hide show
  1. package/dist/common/src/collections/default-collections.d.ts +12 -0
  2. package/dist/common/src/collections/index.d.ts +1 -0
  3. package/dist/common/src/data/query_builder.d.ts +51 -0
  4. package/dist/common/src/index.d.ts +1 -0
  5. package/dist/common/src/util/permissions.d.ts +1 -0
  6. package/dist/{index-BZoAtuqi.js → index-Cr1D21av.js} +11 -3
  7. package/dist/index-Cr1D21av.js.map +1 -0
  8. package/dist/index.es.js +2340 -370
  9. package/dist/index.es.js.map +1 -1
  10. package/dist/index.umd.js +2329 -355
  11. package/dist/index.umd.js.map +1 -1
  12. package/dist/server-core/src/api/logs-routes.d.ts +37 -0
  13. package/dist/server-core/src/api/rest/api-generator.d.ts +6 -0
  14. package/dist/server-core/src/api/types.d.ts +6 -1
  15. package/dist/server-core/src/auth/adapter-middleware.d.ts +7 -3
  16. package/dist/server-core/src/auth/admin-routes.d.ts +3 -3
  17. package/dist/server-core/src/auth/api-keys/api-key-middleware.d.ts +39 -0
  18. package/dist/server-core/src/auth/api-keys/api-key-permission-guard.d.ts +32 -0
  19. package/dist/server-core/src/auth/api-keys/api-key-routes.d.ts +20 -0
  20. package/dist/server-core/src/auth/api-keys/api-key-store.d.ts +35 -0
  21. package/dist/server-core/src/auth/api-keys/api-key-types.d.ts +88 -0
  22. package/dist/server-core/src/auth/api-keys/index.d.ts +17 -0
  23. package/dist/server-core/src/auth/{auth-overrides.d.ts → auth-hooks.d.ts} +69 -11
  24. package/dist/server-core/src/auth/builtin-auth-adapter.d.ts +3 -3
  25. package/dist/server-core/src/auth/index.d.ts +5 -3
  26. package/dist/server-core/src/auth/interfaces.d.ts +93 -3
  27. package/dist/server-core/src/auth/jwt.d.ts +3 -1
  28. package/dist/server-core/src/auth/mfa.d.ts +49 -0
  29. package/dist/server-core/src/auth/middleware.d.ts +7 -0
  30. package/dist/server-core/src/auth/rate-limiter.d.ts +19 -0
  31. package/dist/server-core/src/auth/routes.d.ts +3 -3
  32. package/dist/server-core/src/env.d.ts +6 -0
  33. package/dist/server-core/src/index.d.ts +1 -0
  34. package/dist/server-core/src/init.d.ts +3 -3
  35. package/dist/server-core/src/services/webhook-service.d.ts +29 -0
  36. package/dist/server-core/src/storage/image-transform.d.ts +48 -0
  37. package/dist/server-core/src/storage/index.d.ts +3 -0
  38. package/dist/server-core/src/storage/tus-handler.d.ts +51 -0
  39. package/dist/types/src/controllers/auth.d.ts +2 -24
  40. package/dist/types/src/controllers/client.d.ts +0 -3
  41. package/dist/types/src/controllers/collection_registry.d.ts +1 -1
  42. package/dist/types/src/controllers/data.d.ts +21 -0
  43. package/dist/types/src/controllers/data_driver.d.ts +18 -0
  44. package/dist/types/src/controllers/registry.d.ts +5 -4
  45. package/dist/types/src/rebase_context.d.ts +1 -1
  46. package/dist/types/src/types/auth_adapter.d.ts +2 -4
  47. package/dist/types/src/types/collections.d.ts +0 -4
  48. package/dist/types/src/types/component_ref.d.ts +1 -1
  49. package/dist/types/src/types/cron.d.ts +1 -1
  50. package/dist/types/src/types/entity_views.d.ts +1 -0
  51. package/dist/types/src/types/export_import.d.ts +1 -1
  52. package/dist/types/src/types/formex.d.ts +2 -2
  53. package/dist/types/src/types/properties.d.ts +2 -2
  54. package/dist/types/src/types/translations.d.ts +28 -12
  55. package/dist/types/src/types/user_management_delegate.d.ts +6 -4
  56. package/dist/types/src/users/roles.d.ts +0 -8
  57. package/jest.config.cjs +4 -1
  58. package/package.json +9 -7
  59. package/src/api/ast-schema-editor.ts +4 -4
  60. package/src/api/errors.ts +16 -7
  61. package/src/api/logs-routes.ts +129 -0
  62. package/src/api/rest/api-generator.ts +42 -2
  63. package/src/api/rest/query-parser.ts +37 -1
  64. package/src/api/types.ts +6 -1
  65. package/src/auth/adapter-middleware.ts +20 -4
  66. package/src/auth/admin-routes.ts +39 -14
  67. package/src/auth/api-keys/api-key-middleware.ts +126 -0
  68. package/src/auth/api-keys/api-key-permission-guard.ts +64 -0
  69. package/src/auth/api-keys/api-key-routes.ts +183 -0
  70. package/src/auth/api-keys/api-key-store.ts +317 -0
  71. package/src/auth/api-keys/api-key-types.ts +94 -0
  72. package/src/auth/api-keys/index.ts +37 -0
  73. package/src/auth/{auth-overrides.ts → auth-hooks.ts} +81 -14
  74. package/src/auth/builtin-auth-adapter.ts +31 -19
  75. package/src/auth/index.ts +7 -3
  76. package/src/auth/interfaces.ts +111 -3
  77. package/src/auth/jwt.ts +19 -5
  78. package/src/auth/mfa.ts +160 -0
  79. package/src/auth/middleware.ts +20 -1
  80. package/src/auth/rate-limiter.ts +92 -0
  81. package/src/auth/routes.ts +455 -24
  82. package/src/cron/cron-loader.ts +5 -10
  83. package/src/cron/cron-scheduler.ts +11 -12
  84. package/src/cron/cron-store.ts +8 -7
  85. package/src/env.ts +2 -0
  86. package/src/functions/function-loader.ts +6 -9
  87. package/src/index.ts +1 -2
  88. package/src/init.ts +37 -7
  89. package/src/serve-spa.ts +5 -4
  90. package/src/services/webhook-service.ts +155 -0
  91. package/src/storage/image-transform.ts +202 -0
  92. package/src/storage/index.ts +3 -0
  93. package/src/storage/routes.ts +56 -3
  94. package/src/storage/tus-handler.ts +315 -0
  95. package/src/utils/dev-port.ts +14 -0
  96. package/src/utils/logging.ts +9 -7
  97. package/test/admin-routes.test.ts +74 -7
  98. package/test/api-generator.test.ts +0 -1
  99. package/test/api-key-permission-guard.test.ts +132 -0
  100. package/test/ast-schema-editor.test.ts +26 -0
  101. package/test/auth-routes.test.ts +1 -2
  102. package/test/backend-hooks-admin.test.ts +3 -4
  103. package/test/email-templates.test.ts +169 -0
  104. package/test/function-loader.test.ts +124 -0
  105. package/test/jwt.test.ts +4 -2
  106. package/test/mfa.test.ts +197 -0
  107. package/test/middleware.test.ts +10 -5
  108. package/test/webhook-service.test.ts +249 -0
  109. package/vite.config.ts +3 -2
  110. package/dist/index-BZoAtuqi.js.map +0 -1
  111. package/dist/server-core/src/bootstrappers/index.d.ts +0 -0
  112. package/src/bootstrappers/index.ts +0 -1
  113. package/src/singleton.test.ts +0 -28
package/dist/index.es.js CHANGED
@@ -1,29 +1,33 @@
1
1
  import * as fs$4 from "fs";
2
- import fs__default from "fs";
2
+ import fs__default, { existsSync } from "fs";
3
3
  import * as path$3 from "path";
4
- import path__default from "path";
4
+ import path__default, { join as join$1 } from "path";
5
5
  import require$$0$5, { pathToFileURL } from "url";
6
6
  import { Hono } from "hono";
7
7
  import require$$0$3 from "buffer";
8
8
  import require$$0$4 from "stream";
9
9
  import require$$5, { promisify } from "util";
10
10
  import * as require$$0$2 from "crypto";
11
- import require$$0__default, { randomBytes, createHash, timingSafeEqual as timingSafeEqual$1, scrypt } from "crypto";
11
+ import require$$0__default, { randomBytes, createHash, timingSafeEqual as timingSafeEqual$1, scrypt, createHmac, randomUUID as randomUUID$1 } from "crypto";
12
12
  import { bodyLimit } from "hono/body-limit";
13
13
  import { csrf } from "hono/csrf";
14
14
  import require$$0$a from "child_process";
15
15
  import require$$1 from "https";
16
+ import require$$1$2 from "querystring";
16
17
  import require$$0$7 from "net";
17
18
  import require$$1$1 from "tls";
18
19
  import require$$2 from "assert";
19
20
  import require$$0$6 from "http";
20
- import require$$1$2 from "os";
21
+ import require$$1$3 from "os";
21
22
  import require$$0$8 from "node:events";
22
- import require$$1$3 from "node:process";
23
+ import require$$1$4 from "node:process";
23
24
  import require$$2$1 from "node:util";
24
25
  import require$$0$9 from "events";
25
26
  import { S3Client, PutObjectCommand, HeadObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsV2Command } from "@aws-sdk/client-s3";
26
27
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
28
+ import { mkdir as mkdir$1, unlink as unlink$1, writeFile as writeFile$1, open } from "fs/promises";
29
+ import require$$3 from "zlib";
30
+ import require$$4$1 from "dns";
27
31
  import { cors } from "hono/cors";
28
32
  import { secureHeaders } from "hono/secure-headers";
29
33
  import { serve } from "@hono/node-server";
@@ -211,30 +215,6 @@ var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof win
211
215
  function getDefaultExportFromCjs(x) {
212
216
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x;
213
217
  }
214
- function getAugmentedNamespace(n) {
215
- if (n.__esModule) return n;
216
- var f2 = n.default;
217
- if (typeof f2 == "function") {
218
- var a2 = function a3() {
219
- if (this instanceof a3) {
220
- return Reflect.construct(f2, arguments, this.constructor);
221
- }
222
- return f2.apply(this, arguments);
223
- };
224
- a2.prototype = f2.prototype;
225
- } else a2 = {};
226
- Object.defineProperty(a2, "__esModule", { value: true });
227
- Object.keys(n).forEach(function(k) {
228
- var d2 = Object.getOwnPropertyDescriptor(n, k);
229
- Object.defineProperty(a2, k, d2.get ? d2 : {
230
- enumerable: true,
231
- get: function() {
232
- return n[k];
233
- }
234
- });
235
- });
236
- return a2;
237
- }
238
218
  function commonjsRequire(path2) {
239
219
  throw new Error('Could not dynamically require "' + path2 + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
240
220
  }
@@ -1066,6 +1046,9 @@ function mergeDeep(target, source, ignoreUndefined = false) {
1066
1046
  return output;
1067
1047
  }
1068
1048
  for (const key in source) {
1049
+ if (key === "__proto__" || key === "constructor" || key === "prototype") {
1050
+ continue;
1051
+ }
1069
1052
  if (Object.prototype.hasOwnProperty.call(source, key)) {
1070
1053
  const sourceValue = source[key];
1071
1054
  const outputValue = output[key];
@@ -1165,42 +1148,6 @@ function enumToObjectEntries(enumValues) {
1165
1148
  });
1166
1149
  }
1167
1150
  }
1168
- function getSubcollections(collection) {
1169
- if (collection.childCollections) {
1170
- return collection.childCollections() ?? [];
1171
- }
1172
- if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1173
- return collection.subcollections() ?? [];
1174
- }
1175
- if (getDataSourceCapabilities(collection.driver).supportsRelations && collection.relations) {
1176
- const manyRelations = collection.relations.filter((r) => r.cardinality === "many");
1177
- return manyRelations.map((r) => {
1178
- const target = r.target();
1179
- if (!target) return void 0;
1180
- const relationKey = r.relationName || target.slug;
1181
- let customName;
1182
- if (collection.properties) {
1183
- const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1184
- if (prop && prop[1].name) {
1185
- customName = prop[1].name;
1186
- }
1187
- }
1188
- const baseOverrides = {
1189
- slug: relationKey
1190
- };
1191
- if (customName) {
1192
- baseOverrides.name = customName;
1193
- baseOverrides.singularName = customName;
1194
- }
1195
- const targetWithOverrides = {
1196
- ...target,
1197
- ...baseOverrides
1198
- };
1199
- return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1200
- }).filter((c) => Boolean(c));
1201
- }
1202
- return [];
1203
- }
1204
1151
  function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1205
1152
  if (!relation.target) {
1206
1153
  throw new Error("Relation is missing a `target` collection.");
@@ -1232,6 +1179,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1232
1179
  } else {
1233
1180
  targetCollection = evaluated;
1234
1181
  }
1182
+ } else if (rawTarget && typeof rawTarget === "object") {
1183
+ targetCollection = rawTarget;
1235
1184
  }
1236
1185
  if (!targetCollection) {
1237
1186
  throw new Error("Relation is missing a valid `target` collection.");
@@ -1288,8 +1237,8 @@ function sanitizeRelation(relation, sourceCollection, resolveCollection) {
1288
1237
  } catch (e) {
1289
1238
  }
1290
1239
  if (!foundForeignKey) {
1291
- const keyPrefix = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
1292
- newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix);
1240
+ const keyPrefix2 = newRelation.inverseRelationName ? toSnakeCase(newRelation.inverseRelationName) : sourceName;
1241
+ newRelation.foreignKeyOnTarget = generateForeignKeyName(keyPrefix2);
1293
1242
  }
1294
1243
  }
1295
1244
  } else if (newRelation.cardinality === "many" && newRelation.direction === "inverse") {
@@ -1351,11 +1300,14 @@ function resolveCollectionRelations(collection) {
1351
1300
  const registeredRelationNames = /* @__PURE__ */ new Set();
1352
1301
  if (relCollection.relations) {
1353
1302
  relCollection.relations.forEach((relation) => {
1354
- const normalizedRelation = sanitizeRelation(relation, collection);
1355
- const relationKey = normalizedRelation.relationName;
1356
- if (relationKey) {
1357
- relations[relationKey] = normalizedRelation;
1358
- registeredRelationNames.add(relationKey);
1303
+ try {
1304
+ const normalizedRelation = sanitizeRelation(relation, collection);
1305
+ const relationKey = normalizedRelation.relationName;
1306
+ if (relationKey) {
1307
+ relations[relationKey] = normalizedRelation;
1308
+ registeredRelationNames.add(relationKey);
1309
+ }
1310
+ } catch (e) {
1359
1311
  }
1360
1312
  });
1361
1313
  }
@@ -1403,12 +1355,8 @@ function resolvePropertyRelation({
1403
1355
  overrides: relProp.overrides
1404
1356
  };
1405
1357
  }
1406
- const relation = (sourceCollection.relations ?? []).find((rel) => rel.relationName === relProp.relationName);
1407
- if (!relation) {
1408
- console.warn(`Unrecognized relation format for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1409
- return void 0;
1410
- }
1411
- return relation;
1358
+ console.warn(`Unrecognized or missing relation target for property '${propertyKey}' in collection '${sourceCollection.slug}'`);
1359
+ return void 0;
1412
1360
  }
1413
1361
  function getTableName(collection) {
1414
1362
  if (getDataSourceCapabilities(collection.driver).supportsRelations) {
@@ -1424,6 +1372,43 @@ function findRelation(resolvedRelations, key) {
1424
1372
  if (snakeKey !== key && resolvedRelations[snakeKey]) return resolvedRelations[snakeKey];
1425
1373
  return void 0;
1426
1374
  }
1375
+ function getSubcollections(collection) {
1376
+ if (collection.childCollections) {
1377
+ return collection.childCollections() ?? [];
1378
+ }
1379
+ if (getDataSourceCapabilities(collection.driver).supportsSubcollections && collection.subcollections) {
1380
+ return collection.subcollections() ?? [];
1381
+ }
1382
+ if (getDataSourceCapabilities(collection.driver).supportsRelations) {
1383
+ const resolvedRelations = resolveCollectionRelations(collection);
1384
+ const manyRelations = Object.values(resolvedRelations).filter((r) => r.cardinality === "many");
1385
+ return manyRelations.map((r) => {
1386
+ const target = r.target();
1387
+ if (!target) return void 0;
1388
+ const relationKey = r.relationName || target.slug;
1389
+ let customName;
1390
+ if (collection.properties) {
1391
+ const prop = Object.entries(collection.properties).find(([_, p]) => p.type === "relation" && p.relationName === relationKey);
1392
+ if (prop && prop[1].name) {
1393
+ customName = prop[1].name;
1394
+ }
1395
+ }
1396
+ const baseOverrides = {
1397
+ slug: relationKey
1398
+ };
1399
+ if (customName) {
1400
+ baseOverrides.name = customName;
1401
+ baseOverrides.singularName = customName;
1402
+ }
1403
+ const targetWithOverrides = {
1404
+ ...target,
1405
+ ...baseOverrides
1406
+ };
1407
+ return r.overrides ? mergeDeep(targetWithOverrides, r.overrides) : targetWithOverrides;
1408
+ }).filter((c) => Boolean(c));
1409
+ }
1410
+ return [];
1411
+ }
1427
1412
  var logic = { exports: {} };
1428
1413
  (function(module, exports) {
1429
1414
  (function(root, factory) {
@@ -2331,8 +2316,18 @@ class CollectionRegistry {
2331
2316
  const mergedRelationsRaw = [...extractedRelations];
2332
2317
  for (const manual of manualRelations) {
2333
2318
  const name2 = manual.relationName;
2334
- if (!name2 || !mergedRelationsRaw.find((r) => r.relationName === name2)) {
2319
+ if (!name2) {
2335
2320
  mergedRelationsRaw.push(manual);
2321
+ } else {
2322
+ const existingIndex = mergedRelationsRaw.findIndex((r) => r.relationName === name2);
2323
+ if (existingIndex === -1) {
2324
+ mergedRelationsRaw.push(manual);
2325
+ } else {
2326
+ mergedRelationsRaw[existingIndex] = {
2327
+ ...manual,
2328
+ ...mergedRelationsRaw[existingIndex]
2329
+ };
2330
+ }
2336
2331
  }
2337
2332
  }
2338
2333
  let mergedRelations = mergedRelationsRaw;
@@ -2552,6 +2547,118 @@ class CollectionRegistry {
2552
2547
  };
2553
2548
  }
2554
2549
  }
2550
+ function mapOperator$1(op) {
2551
+ switch (op) {
2552
+ case "==":
2553
+ return "eq";
2554
+ case "!=":
2555
+ return "neq";
2556
+ case ">":
2557
+ return "gt";
2558
+ case ">=":
2559
+ return "gte";
2560
+ case "<":
2561
+ return "lt";
2562
+ case "<=":
2563
+ return "lte";
2564
+ case "array-contains":
2565
+ return "cs";
2566
+ case "array-contains-any":
2567
+ return "csa";
2568
+ case "not-in":
2569
+ return "nin";
2570
+ default:
2571
+ return op;
2572
+ }
2573
+ }
2574
+ class QueryBuilder {
2575
+ constructor(collection) {
2576
+ this.collection = collection;
2577
+ }
2578
+ params = {
2579
+ where: {}
2580
+ };
2581
+ /**
2582
+ * Add a filter condition to your query.
2583
+ * @example
2584
+ * client.collection('users').where('age', '>=', 18).find()
2585
+ */
2586
+ where(column, operator, value) {
2587
+ if (!this.params.where) {
2588
+ this.params.where = {};
2589
+ }
2590
+ const mappedOp = mapOperator$1(operator);
2591
+ let formattedValue = value;
2592
+ if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
2593
+ formattedValue = `(${value.join(",")})`;
2594
+ } else if (value === null) {
2595
+ formattedValue = "null";
2596
+ }
2597
+ this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
2598
+ return this;
2599
+ }
2600
+ /**
2601
+ * Order the results by a specific column.
2602
+ * @example
2603
+ * client.collection('users').orderBy('createdAt', 'desc').find()
2604
+ */
2605
+ orderBy(column, ascending = "asc") {
2606
+ this.params.orderBy = `${column}:${ascending}`;
2607
+ return this;
2608
+ }
2609
+ /**
2610
+ * Limit the number of results returned.
2611
+ */
2612
+ limit(count) {
2613
+ this.params.limit = count;
2614
+ return this;
2615
+ }
2616
+ /**
2617
+ * Skip the first N results.
2618
+ */
2619
+ offset(count) {
2620
+ this.params.offset = count;
2621
+ return this;
2622
+ }
2623
+ /**
2624
+ * Set a free-text search string if supported by the backend.
2625
+ */
2626
+ search(searchString) {
2627
+ this.params.searchString = searchString;
2628
+ return this;
2629
+ }
2630
+ /**
2631
+ * Include related entities in the response.
2632
+ * Relations will be populated with full entity data instead of just IDs.
2633
+ *
2634
+ * @param relations - Relation names to include, or "*" for all.
2635
+ * @example
2636
+ * // Include specific relations
2637
+ * client.data.posts.include("tags", "author").find()
2638
+ *
2639
+ * // Include all relations
2640
+ * client.data.posts.include("*").find()
2641
+ */
2642
+ include(...relations) {
2643
+ this.params.include = relations;
2644
+ return this;
2645
+ }
2646
+ /**
2647
+ * Execute the find query and return the results.
2648
+ */
2649
+ async find() {
2650
+ return this.collection.find(this.params);
2651
+ }
2652
+ /**
2653
+ * Listen to realtime updates matching this query.
2654
+ */
2655
+ listen(onUpdate, onError) {
2656
+ if (!this.collection.listen) {
2657
+ throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
2658
+ }
2659
+ return this.collection.listen(this.params, onUpdate, onError);
2660
+ }
2661
+ }
2555
2662
  class BackendCollectionRegistry extends CollectionRegistry {
2556
2663
  /**
2557
2664
  * Get the available relation keys for a given collection path.
@@ -2731,10 +2838,15 @@ const errorHandler = (err, c) => {
2731
2838
  const issue = error2.code === "42703" ? "column" : "table";
2732
2839
  logMessage = `Database schema mismatch (${issue} missing): ${error2.message}. Did you forget to run migrations ('pnpm db:push' or 'pnpm db:migrate')?`;
2733
2840
  }
2734
- console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
2735
2841
  const causePg = error2.cause && typeof error2.cause === "object" ? error2.cause : void 0;
2736
2842
  const pgErrorCode = causePg?.code || error2.code;
2737
- const suppressStack = pgErrorCode === "42703" || pgErrorCode === "42P01" || statusCode < 500 && code2 === "BAD_REQUEST";
2843
+ const isDbSchemaMismatch = pgErrorCode === "42703" || pgErrorCode === "42P01";
2844
+ if (isDbSchemaMismatch) {
2845
+ console.warn(`⚠️ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
2846
+ } else {
2847
+ console.error(`❌ [API] ${c.req.method} ${c.req.path} → ${statusCode} ${code2}: ${logMessage}`);
2848
+ }
2849
+ const suppressStack = isDbSchemaMismatch || statusCode < 500 && code2 === "BAD_REQUEST";
2738
2850
  if (!suppressStack) {
2739
2851
  console.error(error2.stack || error2);
2740
2852
  }
@@ -2779,7 +2891,7 @@ function codeToStatus(code2) {
2779
2891
  };
2780
2892
  return map2[code2];
2781
2893
  }
2782
- function mapOperator$1(op) {
2894
+ function mapOperator(op) {
2783
2895
  switch (op) {
2784
2896
  case "eq":
2785
2897
  return "==";
@@ -2815,7 +2927,7 @@ function parseQueryOptions(query) {
2815
2927
  options2.offset = (page - 1) * limit;
2816
2928
  }
2817
2929
  options2.where = {};
2818
- const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString"];
2930
+ const reservedQueryKeys = ["limit", "offset", "page", "orderBy", "include", "fields", "searchString", "vector_search", "vector", "vector_distance", "vector_threshold"];
2819
2931
  for (const [key, rawValue] of Object.entries(query)) {
2820
2932
  if (reservedQueryKeys.includes(key)) continue;
2821
2933
  const value = Array.isArray(rawValue) ? rawValue[rawValue.length - 1] : rawValue;
@@ -2824,7 +2936,7 @@ function parseQueryOptions(query) {
2824
2936
  if (parts.length >= 2) {
2825
2937
  const op = parts[0];
2826
2938
  const val = parts.slice(1).join(".");
2827
- const rebaseOp = mapOperator$1(op);
2939
+ const rebaseOp = mapOperator(op);
2828
2940
  if (rebaseOp) {
2829
2941
  let parsedVal = val;
2830
2942
  if (val === "true") parsedVal = true;
@@ -2887,8 +2999,63 @@ function parseQueryOptions(query) {
2887
2999
  const fieldsStr = String(query.fields).trim();
2888
3000
  options2.fields = fieldsStr.split(",").map((s2) => s2.trim()).filter(Boolean);
2889
3001
  }
3002
+ if (query.vector_search && query.vector) {
3003
+ const vectorStr = String(query.vector);
3004
+ let queryVector;
3005
+ try {
3006
+ queryVector = JSON.parse(vectorStr);
3007
+ if (!Array.isArray(queryVector) || !queryVector.every((v) => typeof v === "number")) {
3008
+ throw new Error("Expected array of numbers");
3009
+ }
3010
+ } catch {
3011
+ throw new Error("Invalid vector format. Expected JSON array of numbers, e.g. [0.1,0.2,0.3]");
3012
+ }
3013
+ const distanceParam = query.vector_distance ? String(query.vector_distance) : "cosine";
3014
+ if (distanceParam !== "cosine" && distanceParam !== "l2" && distanceParam !== "inner_product") {
3015
+ throw new Error(`Invalid vector_distance: ${distanceParam}. Expected: cosine, l2, or inner_product`);
3016
+ }
3017
+ const vectorSearch = {
3018
+ property: String(query.vector_search),
3019
+ vector: queryVector,
3020
+ distance: distanceParam
3021
+ };
3022
+ if (query.vector_threshold) {
3023
+ const threshold = parseFloat(String(query.vector_threshold));
3024
+ if (isNaN(threshold)) {
3025
+ throw new Error("Invalid vector_threshold. Expected a number.");
3026
+ }
3027
+ vectorSearch.threshold = threshold;
3028
+ }
3029
+ options2.vectorSearch = vectorSearch;
3030
+ }
2890
3031
  return options2;
2891
3032
  }
3033
+ function httpMethodToOperation(method) {
3034
+ const upper = method.toUpperCase();
3035
+ switch (upper) {
3036
+ case "GET":
3037
+ case "HEAD":
3038
+ case "OPTIONS":
3039
+ return "read";
3040
+ case "POST":
3041
+ case "PUT":
3042
+ case "PATCH":
3043
+ return "write";
3044
+ case "DELETE":
3045
+ return "delete";
3046
+ default:
3047
+ return "read";
3048
+ }
3049
+ }
3050
+ function isOperationAllowed(permissions, collection, operation) {
3051
+ for (const perm of permissions) {
3052
+ const collectionMatch = perm.collection === "*" || perm.collection === collection;
3053
+ if (collectionMatch && perm.operations.includes(operation)) {
3054
+ return true;
3055
+ }
3056
+ }
3057
+ return false;
3058
+ }
2892
3059
  class RestApiGenerator {
2893
3060
  collections;
2894
3061
  router;
@@ -2921,6 +3088,19 @@ class RestApiGenerator {
2921
3088
  this.createSubcollectionRoutes();
2922
3089
  return this.router;
2923
3090
  }
3091
+ /**
3092
+ * Check API key permissions for a collection operation.
3093
+ * Throws 403 if the key doesn't have the required permission.
3094
+ * No-ops if the request is not authenticated via an API key.
3095
+ */
3096
+ enforceApiKeyPermission(c, collectionSlug) {
3097
+ const apiKey = c.get("apiKey");
3098
+ if (!apiKey) return;
3099
+ const operation = httpMethodToOperation(c.req.method);
3100
+ if (!isOperationAllowed(apiKey.permissions, collectionSlug, operation)) {
3101
+ throw ApiError.forbidden(`API key does not have "${operation}" permission for collection "${collectionSlug}"`, "API_KEY_FORBIDDEN");
3102
+ }
3103
+ }
2924
3104
  /**
2925
3105
  * Get the typed RestFetchService from a driver if it exposes one (for include support).
2926
3106
  */
@@ -2934,6 +3114,7 @@ class RestApiGenerator {
2934
3114
  const basePath = `/${collection.slug}`;
2935
3115
  const resolvedCollection = collection;
2936
3116
  this.router.get(`${basePath}/count`, async (c) => {
3117
+ this.enforceApiKeyPermission(c, collection.slug);
2937
3118
  const queryDict = c.req.query();
2938
3119
  const queryOptions = parseQueryOptions(queryDict);
2939
3120
  const searchString = queryDict.searchString;
@@ -2944,6 +3125,7 @@ class RestApiGenerator {
2944
3125
  });
2945
3126
  });
2946
3127
  this.router.get(basePath, async (c) => {
3128
+ this.enforceApiKeyPermission(c, collection.slug);
2947
3129
  const queryDict = c.req.query();
2948
3130
  const queryOptions = parseQueryOptions(queryDict);
2949
3131
  const searchString = queryDict.searchString;
@@ -2958,7 +3140,8 @@ class RestApiGenerator {
2958
3140
  offset: queryOptions.offset,
2959
3141
  orderBy: queryOptions.orderBy?.[0]?.field,
2960
3142
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
2961
- searchString
3143
+ searchString,
3144
+ vectorSearch: queryOptions.vectorSearch
2962
3145
  }, queryOptions.include);
2963
3146
  entities2 = await this.applyAfterReadBatch(collection.slug, entities2, hookCtx);
2964
3147
  const total2 = await this.countRawEntities(driver, resolvedCollection, queryOptions, searchString);
@@ -2986,6 +3169,7 @@ class RestApiGenerator {
2986
3169
  });
2987
3170
  });
2988
3171
  this.router.get(`${basePath}/:id`, async (c) => {
3172
+ this.enforceApiKeyPermission(c, collection.slug);
2989
3173
  const id = c.req.param("id");
2990
3174
  const queryDict = c.req.query();
2991
3175
  const queryOptions = parseQueryOptions(queryDict);
@@ -3016,6 +3200,7 @@ class RestApiGenerator {
3016
3200
  });
3017
3201
  this.router.post(basePath, async (c) => {
3018
3202
  try {
3203
+ this.enforceApiKeyPermission(c, collection.slug);
3019
3204
  const driver = c.get("driver") || this.driver;
3020
3205
  const path2 = collection.slug;
3021
3206
  const hookCtx = this.buildHookContext(c, "POST");
@@ -3044,6 +3229,7 @@ class RestApiGenerator {
3044
3229
  });
3045
3230
  this.router.put(`${basePath}/:id`, async (c) => {
3046
3231
  try {
3232
+ this.enforceApiKeyPermission(c, collection.slug);
3047
3233
  const id = c.req.param("id");
3048
3234
  const driver = c.get("driver") || this.driver;
3049
3235
  const hookCtx = this.buildHookContext(c, "PUT");
@@ -3080,6 +3266,7 @@ class RestApiGenerator {
3080
3266
  }
3081
3267
  });
3082
3268
  this.router.delete(`${basePath}/:id`, async (c) => {
3269
+ this.enforceApiKeyPermission(c, collection.slug);
3083
3270
  const id = c.req.param("id");
3084
3271
  const driver = c.get("driver") || this.driver;
3085
3272
  const hookCtx = this.buildHookContext(c, "DELETE");
@@ -3151,6 +3338,7 @@ class RestApiGenerator {
3151
3338
  const parsed = parseSubPath(rawPath);
3152
3339
  if (!parsed) return next();
3153
3340
  const driver = c.get("driver") || this.driver;
3341
+ this.enforceApiKeyPermission(c, c.req.param("parent"));
3154
3342
  if (parsed.entityId === "count") {
3155
3343
  const queryDict = c.req.query();
3156
3344
  const queryOptions = parseQueryOptions(queryDict);
@@ -3198,6 +3386,7 @@ class RestApiGenerator {
3198
3386
  const parsed = parseSubPath(rawPath);
3199
3387
  if (!parsed || parsed.entityId) return next();
3200
3388
  const driver = c.get("driver") || this.driver;
3389
+ this.enforceApiKeyPermission(c, c.req.param("parent"));
3201
3390
  const body = await c.req.json().catch(() => ({}));
3202
3391
  const entity = await driver.saveEntity({
3203
3392
  path: parsed.collectionPath,
@@ -3213,6 +3402,7 @@ class RestApiGenerator {
3213
3402
  const parsed = parseSubPath(rawPath);
3214
3403
  if (!parsed || !parsed.entityId) return next();
3215
3404
  const driver = c.get("driver") || this.driver;
3405
+ this.enforceApiKeyPermission(c, c.req.param("parent"));
3216
3406
  const body = await c.req.json().catch(() => ({}));
3217
3407
  const entity = await driver.saveEntity({
3218
3408
  path: parsed.collectionPath,
@@ -3229,6 +3419,7 @@ class RestApiGenerator {
3229
3419
  const parsed = parseSubPath(rawPath);
3230
3420
  if (!parsed || !parsed.entityId) return next();
3231
3421
  const driver = c.get("driver") || this.driver;
3422
+ this.enforceApiKeyPermission(c, c.req.param("parent"));
3232
3423
  const existingEntity = await driver.fetchEntity({
3233
3424
  path: parsed.collectionPath,
3234
3425
  entityId: parsed.entityId
@@ -3294,7 +3485,8 @@ class RestApiGenerator {
3294
3485
  orderBy: queryOptions.orderBy?.[0]?.field,
3295
3486
  order: queryOptions.orderBy?.[0]?.direction === "desc" ? "desc" : "asc",
3296
3487
  startAfter: queryOptions.offset ? String(queryOptions.offset) : void 0,
3297
- searchString
3488
+ searchString,
3489
+ vectorSearch: queryOptions.vectorSearch
3298
3490
  });
3299
3491
  return entities.map((entity) => this.flattenEntity(entity));
3300
3492
  }
@@ -4820,7 +5012,7 @@ var cmp_1 = cmp$1;
4820
5012
  const SemVer$6 = semver$4;
4821
5013
  const parse$6 = parse_1;
4822
5014
  const { safeRe: re, t } = reExports;
4823
- const coerce$1 = (version2, options2) => {
5015
+ const coerce$2 = (version2, options2) => {
4824
5016
  if (version2 instanceof SemVer$6) {
4825
5017
  return version2;
4826
5018
  }
@@ -4855,7 +5047,7 @@ const coerce$1 = (version2, options2) => {
4855
5047
  const build = options2.includePrerelease && match[6] ? `+${match[6]}` : "";
4856
5048
  return parse$6(`${major2}.${minor2}.${patch2}${prerelease2}${build}`, options2);
4857
5049
  };
4858
- var coerce_1 = coerce$1;
5050
+ var coerce_1 = coerce$2;
4859
5051
  const parse$5 = parse_1;
4860
5052
  const constants$1 = constants$2;
4861
5053
  const SemVer$5 = semver$4;
@@ -5141,19 +5333,19 @@ function requireRange() {
5141
5333
  const replaceCaret = (comp, options2) => {
5142
5334
  debug2("caret", comp, options2);
5143
5335
  const r = options2.loose ? re2[t2.CARETLOOSE] : re2[t2.CARET];
5144
- const z = options2.includePrerelease ? "-0" : "";
5336
+ const z2 = options2.includePrerelease ? "-0" : "";
5145
5337
  return comp.replace(r, (_, M, m2, p, pr) => {
5146
5338
  debug2("caret", comp, _, M, m2, p, pr);
5147
5339
  let ret;
5148
5340
  if (isX(M)) {
5149
5341
  ret = "";
5150
5342
  } else if (isX(m2)) {
5151
- ret = `>=${M}.0.0${z} <${+M + 1}.0.0-0`;
5343
+ ret = `>=${M}.0.0${z2} <${+M + 1}.0.0-0`;
5152
5344
  } else if (isX(p)) {
5153
5345
  if (M === "0") {
5154
- ret = `>=${M}.${m2}.0${z} <${M}.${+m2 + 1}.0-0`;
5346
+ ret = `>=${M}.${m2}.0${z2} <${M}.${+m2 + 1}.0-0`;
5155
5347
  } else {
5156
- ret = `>=${M}.${m2}.0${z} <${+M + 1}.0.0-0`;
5348
+ ret = `>=${M}.${m2}.0${z2} <${+M + 1}.0.0-0`;
5157
5349
  }
5158
5350
  } else if (pr) {
5159
5351
  debug2("replaceCaret pr", pr);
@@ -5170,9 +5362,9 @@ function requireRange() {
5170
5362
  debug2("no pr");
5171
5363
  if (M === "0") {
5172
5364
  if (m2 === "0") {
5173
- ret = `>=${M}.${m2}.${p}${z} <${M}.${m2}.${+p + 1}-0`;
5365
+ ret = `>=${M}.${m2}.${p}${z2} <${M}.${m2}.${+p + 1}-0`;
5174
5366
  } else {
5175
- ret = `>=${M}.${m2}.${p}${z} <${M}.${+m2 + 1}.0-0`;
5367
+ ret = `>=${M}.${m2}.${p}${z2} <${M}.${+m2 + 1}.0-0`;
5176
5368
  }
5177
5369
  } else {
5178
5370
  ret = `>=${M}.${m2}.${p} <${+M + 1}.0.0-0`;
@@ -5829,7 +6021,7 @@ const neq = neq_1;
5829
6021
  const gte = gte_1;
5830
6022
  const lte = lte_1;
5831
6023
  const cmp = cmp_1;
5832
- const coerce = coerce_1;
6024
+ const coerce$1 = coerce_1;
5833
6025
  const truncate = truncate_1;
5834
6026
  const Comparator = requireComparator();
5835
6027
  const Range = requireRange();
@@ -5868,7 +6060,7 @@ var semver$3 = {
5868
6060
  gte,
5869
6061
  lte,
5870
6062
  cmp,
5871
- coerce,
6063
+ coerce: coerce$1,
5872
6064
  truncate,
5873
6065
  Comparator,
5874
6066
  Range,
@@ -6749,7 +6941,7 @@ var jsonwebtoken = {
6749
6941
  NotBeforeError: NotBeforeError_1,
6750
6942
  TokenExpiredError: TokenExpiredError_1
6751
6943
  };
6752
- const jwt = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
6944
+ const jwt$1 = /* @__PURE__ */ getDefaultExportFromCjs(jsonwebtoken);
6753
6945
  let jwtConfig = {
6754
6946
  secret: "",
6755
6947
  accessExpiresIn: "1h",
@@ -6768,15 +6960,17 @@ function configureJwt(config) {
6768
6960
  ...config
6769
6961
  };
6770
6962
  }
6771
- function generateAccessToken(userId, roles) {
6963
+ function generateAccessToken(userId, roles, aal = "aal1", customClaims) {
6772
6964
  if (!jwtConfig.secret) {
6773
6965
  throw new Error("JWT secret not configured. Call configureJwt() first.");
6774
6966
  }
6775
6967
  const payload = {
6776
6968
  userId,
6777
- roles
6969
+ roles,
6970
+ aal,
6971
+ ...customClaims
6778
6972
  };
6779
- return jwt.sign(payload, jwtConfig.secret, {
6973
+ return jwt$1.sign(payload, jwtConfig.secret, {
6780
6974
  expiresIn: jwtConfig.accessExpiresIn,
6781
6975
  algorithm: "HS256"
6782
6976
  });
@@ -6810,7 +7004,7 @@ function verifyAccessToken(token) {
6810
7004
  throw new Error("JWT secret not configured. Call configureJwt() first.");
6811
7005
  }
6812
7006
  try {
6813
- const decoded = jwt.verify(token, jwtConfig.secret, {
7007
+ const decoded = jwt$1.verify(token, jwtConfig.secret, {
6814
7008
  algorithms: ["HS256"]
6815
7009
  });
6816
7010
  const id = decoded.userId || decoded.uid || decoded.sub;
@@ -6818,9 +7012,11 @@ function verifyAccessToken(token) {
6818
7012
  console.error("[JWT] Verification failed: missing id in payload", decoded);
6819
7013
  return null;
6820
7014
  }
7015
+ const aal = decoded.aal === "aal1" || decoded.aal === "aal2" ? decoded.aal : "aal1";
6821
7016
  return {
6822
7017
  userId: id,
6823
- roles: decoded.roles || []
7018
+ roles: decoded.roles || [],
7019
+ aal
6824
7020
  };
6825
7021
  } catch (error2) {
6826
7022
  console.error("[JWT] Verification failed:", error2, "Token start:", token.substring(0, 15));
@@ -6860,6 +7056,17 @@ function getRefreshTokenExpiry() {
6860
7056
  }
6861
7057
  return new Date(Date.now() + ms2);
6862
7058
  }
7059
+ const jwt = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
7060
+ __proto__: null,
7061
+ configureJwt,
7062
+ generateAccessToken,
7063
+ generateRefreshToken,
7064
+ getAccessTokenExpiry,
7065
+ getAccessTokenExpiryMs,
7066
+ getRefreshTokenExpiry,
7067
+ hashRefreshToken,
7068
+ verifyAccessToken
7069
+ }, Symbol.toStringTag, { value: "Module" }));
6863
7070
  function isRLSScopedDriver(driver) {
6864
7071
  return "withAuth" in driver && typeof driver.withAuth === "function";
6865
7072
  }
@@ -6882,6 +7089,81 @@ function safeCompare(a2, b) {
6882
7089
  return false;
6883
7090
  }
6884
7091
  }
7092
+ function isApiKeyToken(token) {
7093
+ return token.startsWith("rk_");
7094
+ }
7095
+ function hashToken$2(token) {
7096
+ return createHash("sha256").update(token).digest("hex");
7097
+ }
7098
+ async function validateApiKey(c, token, options2) {
7099
+ const {
7100
+ store,
7101
+ driver
7102
+ } = options2;
7103
+ const hash = hashToken$2(token);
7104
+ const apiKey = await store.findByKeyHash(hash);
7105
+ if (!apiKey) {
7106
+ return c.json({
7107
+ error: {
7108
+ message: "Invalid API key",
7109
+ code: "UNAUTHORIZED"
7110
+ }
7111
+ }, 401);
7112
+ }
7113
+ if (apiKey.revoked_at) {
7114
+ return c.json({
7115
+ error: {
7116
+ message: "API key has been revoked",
7117
+ code: "UNAUTHORIZED"
7118
+ }
7119
+ }, 401);
7120
+ }
7121
+ if (apiKey.expires_at && new Date(apiKey.expires_at) < /* @__PURE__ */ new Date()) {
7122
+ return c.json({
7123
+ error: {
7124
+ message: "API key has expired",
7125
+ code: "UNAUTHORIZED"
7126
+ }
7127
+ }, 401);
7128
+ }
7129
+ const userId = `api-key:${apiKey.id}`;
7130
+ c.set("user", {
7131
+ userId,
7132
+ roles: []
7133
+ });
7134
+ const masked = {
7135
+ id: apiKey.id,
7136
+ name: apiKey.name,
7137
+ key_prefix: apiKey.key_prefix,
7138
+ permissions: apiKey.permissions,
7139
+ rate_limit: apiKey.rate_limit,
7140
+ created_by: apiKey.created_by,
7141
+ created_at: apiKey.created_at,
7142
+ updated_at: apiKey.updated_at,
7143
+ last_used_at: apiKey.last_used_at,
7144
+ expires_at: apiKey.expires_at,
7145
+ revoked_at: apiKey.revoked_at
7146
+ };
7147
+ c.set("apiKey", masked);
7148
+ try {
7149
+ const scopedDriver = await scopeDataDriver(driver, {
7150
+ uid: userId,
7151
+ roles: ["service"]
7152
+ });
7153
+ c.set("driver", scopedDriver);
7154
+ } catch (error2) {
7155
+ console.error("[AUTH] RLS scoping failed for API key:", error2);
7156
+ return c.json({
7157
+ error: {
7158
+ message: "Internal authentication error",
7159
+ code: "INTERNAL_ERROR"
7160
+ }
7161
+ }, 500);
7162
+ }
7163
+ store.updateLastUsed(apiKey.id).catch(() => {
7164
+ });
7165
+ return true;
7166
+ }
6885
7167
  const requireAuth = async (c, next) => {
6886
7168
  const authHeader = c.req.header("authorization");
6887
7169
  const queryToken = c.req.query("token");
@@ -6988,7 +7270,8 @@ function createAuthMiddleware(options2) {
6988
7270
  driver,
6989
7271
  requireAuth: enforceAuth = true,
6990
7272
  validator,
6991
- serviceKey
7273
+ serviceKey,
7274
+ apiKeyStore
6992
7275
  } = options2;
6993
7276
  return async (c, next) => {
6994
7277
  if (validator) {
@@ -7063,6 +7346,14 @@ function createAuthMiddleware(options2) {
7063
7346
  }
7064
7347
  }, 500);
7065
7348
  }
7349
+ } else if (apiKeyStore && isApiKeyToken(token)) {
7350
+ const result = await validateApiKey(c, token, {
7351
+ store: apiKeyStore,
7352
+ driver
7353
+ });
7354
+ if (result !== true) {
7355
+ return result;
7356
+ }
7066
7357
  } else {
7067
7358
  const payload = extractUserFromToken(token);
7068
7359
  if (payload) {
@@ -7123,9 +7414,22 @@ function createAdapterAuthMiddleware(options2) {
7123
7414
  const {
7124
7415
  adapter,
7125
7416
  driver,
7126
- requireAuth: enforceAuth = true
7417
+ requireAuth: enforceAuth = true,
7418
+ apiKeyStore
7127
7419
  } = options2;
7128
7420
  return async (c, next) => {
7421
+ if (apiKeyStore) {
7422
+ const authHeader = c.req.header("authorization") || "";
7423
+ const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : "";
7424
+ if (token.startsWith("rk_")) {
7425
+ const result = await validateApiKey(c, token, {
7426
+ store: apiKeyStore,
7427
+ driver
7428
+ });
7429
+ if (result === true) return next();
7430
+ return result;
7431
+ }
7432
+ }
7129
7433
  let authenticatedUser = null;
7130
7434
  try {
7131
7435
  authenticatedUser = await adapter.verifyRequest(c.req.raw);
@@ -7221,11 +7525,11 @@ async function verifyPassword(password, storedHash) {
7221
7525
  const derivedKey = await scryptAsync(password, salt, KEY_LENGTH);
7222
7526
  return timingSafeEqual$1(derivedKey, storedKey);
7223
7527
  }
7224
- function resolveAuthOverrides(overrides) {
7528
+ function resolveAuthHooks(hooks) {
7225
7529
  return {
7226
- hashPassword: overrides?.hashPassword ?? hashPassword,
7227
- verifyPassword: overrides?.verifyPassword ?? verifyPassword,
7228
- validatePasswordStrength: overrides?.validatePasswordStrength ?? validatePasswordStrength
7530
+ hashPassword: hooks?.hashPassword ?? hashPassword,
7531
+ verifyPassword: hooks?.verifyPassword ?? verifyPassword,
7532
+ validatePasswordStrength: hooks?.validatePasswordStrength ?? validatePasswordStrength
7229
7533
  };
7230
7534
  }
7231
7535
  function getGreeting(user) {
@@ -7627,6 +7931,63 @@ const strictAuthLimiter = createRateLimiter({
7627
7931
  limit: 50,
7628
7932
  message: "Too many requests to this sensitive endpoint, please try again later."
7629
7933
  });
7934
+ function apiKeyKeyGenerator(c) {
7935
+ const apiKey = c.get("apiKey");
7936
+ if (apiKey) {
7937
+ return `api-key:${apiKey.id}`;
7938
+ }
7939
+ return defaultKeyGenerator(c);
7940
+ }
7941
+ function createApiKeyRateLimiter(defaultLimit = 1e3, windowMs = 15 * 60 * 1e3) {
7942
+ const store = /* @__PURE__ */ new Map();
7943
+ const cleanupInterval = setInterval(() => {
7944
+ const now = Date.now();
7945
+ for (const [key, entry] of store.entries()) {
7946
+ entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
7947
+ if (entry.timestamps.length === 0) {
7948
+ store.delete(key);
7949
+ }
7950
+ }
7951
+ }, windowMs);
7952
+ if (cleanupInterval.unref) {
7953
+ cleanupInterval.unref();
7954
+ }
7955
+ return async (c, next) => {
7956
+ const apiKey = c.get("apiKey");
7957
+ if (!apiKey) {
7958
+ return next();
7959
+ }
7960
+ const limit = apiKey.rate_limit ?? defaultLimit;
7961
+ const key = `api-key:${apiKey.id}`;
7962
+ const now = Date.now();
7963
+ let entry = store.get(key);
7964
+ if (!entry) {
7965
+ entry = {
7966
+ timestamps: []
7967
+ };
7968
+ store.set(key, entry);
7969
+ }
7970
+ entry.timestamps = entry.timestamps.filter((t2) => now - t2 < windowMs);
7971
+ if (entry.timestamps.length >= limit) {
7972
+ const retryAfterMs = entry.timestamps[0] + windowMs - now;
7973
+ const retryAfterSec = Math.ceil(retryAfterMs / 1e3);
7974
+ c.header("Retry-After", String(retryAfterSec));
7975
+ c.header("X-RateLimit-Limit", String(limit));
7976
+ c.header("X-RateLimit-Remaining", "0");
7977
+ c.header("X-RateLimit-Reset", String(Math.ceil((now + retryAfterMs) / 1e3)));
7978
+ return c.json({
7979
+ error: {
7980
+ message: "API key rate limit exceeded, please try again later.",
7981
+ code: "RATE_LIMITED"
7982
+ }
7983
+ }, 429);
7984
+ }
7985
+ entry.timestamps.push(now);
7986
+ c.header("X-RateLimit-Limit", String(limit));
7987
+ c.header("X-RateLimit-Remaining", String(limit - entry.timestamps.length));
7988
+ return next();
7989
+ };
7990
+ }
7630
7991
  var util$3;
7631
7992
  (function(util2) {
7632
7993
  util2.assertEqual = (_) => {
@@ -7777,6 +8138,10 @@ const ZodIssueCode = util$3.arrayToEnum([
7777
8138
  "not_multiple_of",
7778
8139
  "not_finite"
7779
8140
  ]);
8141
+ const quotelessJson = (obj) => {
8142
+ const json = JSON.stringify(obj, null, 2);
8143
+ return json.replace(/"([^"]+)":/g, "$1:");
8144
+ };
7780
8145
  class ZodError extends Error {
7781
8146
  get errors() {
7782
8147
  return this.issues;
@@ -7972,6 +8337,9 @@ const errorMap = (issue, _ctx) => {
7972
8337
  return { message };
7973
8338
  };
7974
8339
  let overrideErrorMap = errorMap;
8340
+ function setErrorMap(map2) {
8341
+ overrideErrorMap = map2;
8342
+ }
7975
8343
  function getErrorMap() {
7976
8344
  return overrideErrorMap;
7977
8345
  }
@@ -8000,6 +8368,7 @@ const makeIssue = (params) => {
8000
8368
  message: errorMessage
8001
8369
  };
8002
8370
  };
8371
+ const EMPTY_PATH = [];
8003
8372
  function addIssueToContext(ctx, issueData) {
8004
8373
  const overrideMap = getErrorMap();
8005
8374
  const issue = makeIssue({
@@ -10290,6 +10659,113 @@ ZodUnion.create = (types2, params) => {
10290
10659
  ...processCreateParams(params)
10291
10660
  });
10292
10661
  };
10662
+ const getDiscriminator = (type) => {
10663
+ if (type instanceof ZodLazy) {
10664
+ return getDiscriminator(type.schema);
10665
+ } else if (type instanceof ZodEffects) {
10666
+ return getDiscriminator(type.innerType());
10667
+ } else if (type instanceof ZodLiteral) {
10668
+ return [type.value];
10669
+ } else if (type instanceof ZodEnum) {
10670
+ return type.options;
10671
+ } else if (type instanceof ZodNativeEnum) {
10672
+ return util$3.objectValues(type.enum);
10673
+ } else if (type instanceof ZodDefault) {
10674
+ return getDiscriminator(type._def.innerType);
10675
+ } else if (type instanceof ZodUndefined) {
10676
+ return [void 0];
10677
+ } else if (type instanceof ZodNull) {
10678
+ return [null];
10679
+ } else if (type instanceof ZodOptional) {
10680
+ return [void 0, ...getDiscriminator(type.unwrap())];
10681
+ } else if (type instanceof ZodNullable) {
10682
+ return [null, ...getDiscriminator(type.unwrap())];
10683
+ } else if (type instanceof ZodBranded) {
10684
+ return getDiscriminator(type.unwrap());
10685
+ } else if (type instanceof ZodReadonly) {
10686
+ return getDiscriminator(type.unwrap());
10687
+ } else if (type instanceof ZodCatch) {
10688
+ return getDiscriminator(type._def.innerType);
10689
+ } else {
10690
+ return [];
10691
+ }
10692
+ };
10693
+ class ZodDiscriminatedUnion extends ZodType {
10694
+ _parse(input) {
10695
+ const { ctx } = this._processInputParams(input);
10696
+ if (ctx.parsedType !== ZodParsedType.object) {
10697
+ addIssueToContext(ctx, {
10698
+ code: ZodIssueCode.invalid_type,
10699
+ expected: ZodParsedType.object,
10700
+ received: ctx.parsedType
10701
+ });
10702
+ return INVALID;
10703
+ }
10704
+ const discriminator = this.discriminator;
10705
+ const discriminatorValue = ctx.data[discriminator];
10706
+ const option = this.optionsMap.get(discriminatorValue);
10707
+ if (!option) {
10708
+ addIssueToContext(ctx, {
10709
+ code: ZodIssueCode.invalid_union_discriminator,
10710
+ options: Array.from(this.optionsMap.keys()),
10711
+ path: [discriminator]
10712
+ });
10713
+ return INVALID;
10714
+ }
10715
+ if (ctx.common.async) {
10716
+ return option._parseAsync({
10717
+ data: ctx.data,
10718
+ path: ctx.path,
10719
+ parent: ctx
10720
+ });
10721
+ } else {
10722
+ return option._parseSync({
10723
+ data: ctx.data,
10724
+ path: ctx.path,
10725
+ parent: ctx
10726
+ });
10727
+ }
10728
+ }
10729
+ get discriminator() {
10730
+ return this._def.discriminator;
10731
+ }
10732
+ get options() {
10733
+ return this._def.options;
10734
+ }
10735
+ get optionsMap() {
10736
+ return this._def.optionsMap;
10737
+ }
10738
+ /**
10739
+ * The constructor of the discriminated union schema. Its behaviour is very similar to that of the normal z.union() constructor.
10740
+ * However, it only allows a union of objects, all of which need to share a discriminator property. This property must
10741
+ * have a different value for each object in the union.
10742
+ * @param discriminator the name of the discriminator property
10743
+ * @param types an array of object schemas
10744
+ * @param params
10745
+ */
10746
+ static create(discriminator, options2, params) {
10747
+ const optionsMap = /* @__PURE__ */ new Map();
10748
+ for (const type of options2) {
10749
+ const discriminatorValues = getDiscriminator(type.shape[discriminator]);
10750
+ if (!discriminatorValues.length) {
10751
+ throw new Error(`A discriminator value for key \`${discriminator}\` could not be extracted from all schema options`);
10752
+ }
10753
+ for (const value of discriminatorValues) {
10754
+ if (optionsMap.has(value)) {
10755
+ throw new Error(`Discriminator property ${String(discriminator)} has duplicate value ${String(value)}`);
10756
+ }
10757
+ optionsMap.set(value, type);
10758
+ }
10759
+ }
10760
+ return new ZodDiscriminatedUnion({
10761
+ typeName: ZodFirstPartyTypeKind.ZodDiscriminatedUnion,
10762
+ discriminator,
10763
+ options: options2,
10764
+ optionsMap,
10765
+ ...processCreateParams(params)
10766
+ });
10767
+ }
10768
+ }
10293
10769
  function mergeValues(a2, b) {
10294
10770
  const aType = getParsedType(a2);
10295
10771
  const bType = getParsedType(b);
@@ -10448,6 +10924,59 @@ ZodTuple.create = (schemas, params) => {
10448
10924
  ...processCreateParams(params)
10449
10925
  });
10450
10926
  };
10927
+ class ZodRecord extends ZodType {
10928
+ get keySchema() {
10929
+ return this._def.keyType;
10930
+ }
10931
+ get valueSchema() {
10932
+ return this._def.valueType;
10933
+ }
10934
+ _parse(input) {
10935
+ const { status, ctx } = this._processInputParams(input);
10936
+ if (ctx.parsedType !== ZodParsedType.object) {
10937
+ addIssueToContext(ctx, {
10938
+ code: ZodIssueCode.invalid_type,
10939
+ expected: ZodParsedType.object,
10940
+ received: ctx.parsedType
10941
+ });
10942
+ return INVALID;
10943
+ }
10944
+ const pairs = [];
10945
+ const keyType = this._def.keyType;
10946
+ const valueType = this._def.valueType;
10947
+ for (const key in ctx.data) {
10948
+ pairs.push({
10949
+ key: keyType._parse(new ParseInputLazyPath(ctx, key, ctx.path, key)),
10950
+ value: valueType._parse(new ParseInputLazyPath(ctx, ctx.data[key], ctx.path, key)),
10951
+ alwaysSet: key in ctx.data
10952
+ });
10953
+ }
10954
+ if (ctx.common.async) {
10955
+ return ParseStatus.mergeObjectAsync(status, pairs);
10956
+ } else {
10957
+ return ParseStatus.mergeObjectSync(status, pairs);
10958
+ }
10959
+ }
10960
+ get element() {
10961
+ return this._def.valueType;
10962
+ }
10963
+ static create(first, second, third) {
10964
+ if (second instanceof ZodType) {
10965
+ return new ZodRecord({
10966
+ keyType: first,
10967
+ valueType: second,
10968
+ typeName: ZodFirstPartyTypeKind.ZodRecord,
10969
+ ...processCreateParams(third)
10970
+ });
10971
+ }
10972
+ return new ZodRecord({
10973
+ keyType: ZodString.create(),
10974
+ valueType: first,
10975
+ typeName: ZodFirstPartyTypeKind.ZodRecord,
10976
+ ...processCreateParams(second)
10977
+ });
10978
+ }
10979
+ }
10451
10980
  class ZodMap extends ZodType {
10452
10981
  get keySchema() {
10453
10982
  return this._def.keyType;
@@ -10599,6 +11128,111 @@ ZodSet.create = (valueType, params) => {
10599
11128
  ...processCreateParams(params)
10600
11129
  });
10601
11130
  };
11131
+ class ZodFunction extends ZodType {
11132
+ constructor() {
11133
+ super(...arguments);
11134
+ this.validate = this.implement;
11135
+ }
11136
+ _parse(input) {
11137
+ const { ctx } = this._processInputParams(input);
11138
+ if (ctx.parsedType !== ZodParsedType.function) {
11139
+ addIssueToContext(ctx, {
11140
+ code: ZodIssueCode.invalid_type,
11141
+ expected: ZodParsedType.function,
11142
+ received: ctx.parsedType
11143
+ });
11144
+ return INVALID;
11145
+ }
11146
+ function makeArgsIssue(args, error2) {
11147
+ return makeIssue({
11148
+ data: args,
11149
+ path: ctx.path,
11150
+ errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
11151
+ issueData: {
11152
+ code: ZodIssueCode.invalid_arguments,
11153
+ argumentsError: error2
11154
+ }
11155
+ });
11156
+ }
11157
+ function makeReturnsIssue(returns, error2) {
11158
+ return makeIssue({
11159
+ data: returns,
11160
+ path: ctx.path,
11161
+ errorMaps: [ctx.common.contextualErrorMap, ctx.schemaErrorMap, getErrorMap(), errorMap].filter((x) => !!x),
11162
+ issueData: {
11163
+ code: ZodIssueCode.invalid_return_type,
11164
+ returnTypeError: error2
11165
+ }
11166
+ });
11167
+ }
11168
+ const params = { errorMap: ctx.common.contextualErrorMap };
11169
+ const fn = ctx.data;
11170
+ if (this._def.returns instanceof ZodPromise) {
11171
+ const me = this;
11172
+ return OK(async function(...args) {
11173
+ const error2 = new ZodError([]);
11174
+ const parsedArgs = await me._def.args.parseAsync(args, params).catch((e) => {
11175
+ error2.addIssue(makeArgsIssue(args, e));
11176
+ throw error2;
11177
+ });
11178
+ const result = await Reflect.apply(fn, this, parsedArgs);
11179
+ const parsedReturns = await me._def.returns._def.type.parseAsync(result, params).catch((e) => {
11180
+ error2.addIssue(makeReturnsIssue(result, e));
11181
+ throw error2;
11182
+ });
11183
+ return parsedReturns;
11184
+ });
11185
+ } else {
11186
+ const me = this;
11187
+ return OK(function(...args) {
11188
+ const parsedArgs = me._def.args.safeParse(args, params);
11189
+ if (!parsedArgs.success) {
11190
+ throw new ZodError([makeArgsIssue(args, parsedArgs.error)]);
11191
+ }
11192
+ const result = Reflect.apply(fn, this, parsedArgs.data);
11193
+ const parsedReturns = me._def.returns.safeParse(result, params);
11194
+ if (!parsedReturns.success) {
11195
+ throw new ZodError([makeReturnsIssue(result, parsedReturns.error)]);
11196
+ }
11197
+ return parsedReturns.data;
11198
+ });
11199
+ }
11200
+ }
11201
+ parameters() {
11202
+ return this._def.args;
11203
+ }
11204
+ returnType() {
11205
+ return this._def.returns;
11206
+ }
11207
+ args(...items) {
11208
+ return new ZodFunction({
11209
+ ...this._def,
11210
+ args: ZodTuple.create(items).rest(ZodUnknown.create())
11211
+ });
11212
+ }
11213
+ returns(returnType) {
11214
+ return new ZodFunction({
11215
+ ...this._def,
11216
+ returns: returnType
11217
+ });
11218
+ }
11219
+ implement(func) {
11220
+ const validatedFunc = this.parse(func);
11221
+ return validatedFunc;
11222
+ }
11223
+ strictImplement(func) {
11224
+ const validatedFunc = this.parse(func);
11225
+ return validatedFunc;
11226
+ }
11227
+ static create(args, returns, params) {
11228
+ return new ZodFunction({
11229
+ args: args ? args : ZodTuple.create([]).rest(ZodUnknown.create()),
11230
+ returns: returns || ZodUnknown.create(),
11231
+ typeName: ZodFirstPartyTypeKind.ZodFunction,
11232
+ ...processCreateParams(params)
11233
+ });
11234
+ }
11235
+ }
10602
11236
  class ZodLazy extends ZodType {
10603
11237
  get schema() {
10604
11238
  return this._def.getter();
@@ -11056,6 +11690,7 @@ ZodNaN.create = (params) => {
11056
11690
  ...processCreateParams(params)
11057
11691
  });
11058
11692
  };
11693
+ const BRAND = Symbol("zod_brand");
11059
11694
  class ZodBranded extends ZodType {
11060
11695
  _parse(input) {
11061
11696
  const { ctx } = this._processInputParams(input);
@@ -11147,6 +11782,36 @@ ZodReadonly.create = (type, params) => {
11147
11782
  ...processCreateParams(params)
11148
11783
  });
11149
11784
  };
11785
+ function cleanParams(params, data) {
11786
+ const p = typeof params === "function" ? params(data) : typeof params === "string" ? { message: params } : params;
11787
+ const p2 = typeof p === "string" ? { message: p } : p;
11788
+ return p2;
11789
+ }
11790
+ function custom(check, _params = {}, fatal) {
11791
+ if (check)
11792
+ return ZodAny.create().superRefine((data, ctx) => {
11793
+ const r = check(data);
11794
+ if (r instanceof Promise) {
11795
+ return r.then((r2) => {
11796
+ if (!r2) {
11797
+ const params = cleanParams(_params, data);
11798
+ const _fatal = params.fatal ?? fatal ?? true;
11799
+ ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
11800
+ }
11801
+ });
11802
+ }
11803
+ if (!r) {
11804
+ const params = cleanParams(_params, data);
11805
+ const _fatal = params.fatal ?? fatal ?? true;
11806
+ ctx.addIssue({ code: "custom", ...params, fatal: _fatal });
11807
+ }
11808
+ return;
11809
+ });
11810
+ return ZodAny.create();
11811
+ }
11812
+ const late = {
11813
+ object: ZodObject.lazycreate
11814
+ };
11150
11815
  var ZodFirstPartyTypeKind;
11151
11816
  (function(ZodFirstPartyTypeKind2) {
11152
11817
  ZodFirstPartyTypeKind2["ZodString"] = "ZodString";
@@ -11186,17 +11851,251 @@ var ZodFirstPartyTypeKind;
11186
11851
  ZodFirstPartyTypeKind2["ZodPipeline"] = "ZodPipeline";
11187
11852
  ZodFirstPartyTypeKind2["ZodReadonly"] = "ZodReadonly";
11188
11853
  })(ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {}));
11854
+ const instanceOfType = (cls, params = {
11855
+ message: `Input not instance of ${cls.name}`
11856
+ }) => custom((data) => data instanceof cls, params);
11189
11857
  const stringType = ZodString.create;
11190
- ZodNever.create;
11191
- ZodArray.create;
11858
+ const numberType = ZodNumber.create;
11859
+ const nanType = ZodNaN.create;
11860
+ const bigIntType = ZodBigInt.create;
11861
+ const booleanType = ZodBoolean.create;
11862
+ const dateType = ZodDate.create;
11863
+ const symbolType = ZodSymbol.create;
11864
+ const undefinedType = ZodUndefined.create;
11865
+ const nullType = ZodNull.create;
11866
+ const anyType = ZodAny.create;
11867
+ const unknownType = ZodUnknown.create;
11868
+ const neverType = ZodNever.create;
11869
+ const voidType = ZodVoid.create;
11870
+ const arrayType = ZodArray.create;
11192
11871
  const objectType = ZodObject.create;
11193
- ZodUnion.create;
11194
- ZodIntersection.create;
11195
- ZodTuple.create;
11872
+ const strictObjectType = ZodObject.strictCreate;
11873
+ const unionType = ZodUnion.create;
11874
+ const discriminatedUnionType = ZodDiscriminatedUnion.create;
11875
+ const intersectionType = ZodIntersection.create;
11876
+ const tupleType = ZodTuple.create;
11877
+ const recordType = ZodRecord.create;
11878
+ const mapType = ZodMap.create;
11879
+ const setType = ZodSet.create;
11880
+ const functionType = ZodFunction.create;
11881
+ const lazyType = ZodLazy.create;
11882
+ const literalType = ZodLiteral.create;
11196
11883
  const enumType = ZodEnum.create;
11197
- ZodPromise.create;
11198
- ZodOptional.create;
11199
- ZodNullable.create;
11884
+ const nativeEnumType = ZodNativeEnum.create;
11885
+ const promiseType = ZodPromise.create;
11886
+ const effectsType = ZodEffects.create;
11887
+ const optionalType = ZodOptional.create;
11888
+ const nullableType = ZodNullable.create;
11889
+ const preprocessType = ZodEffects.createWithPreprocess;
11890
+ const pipelineType = ZodPipeline.create;
11891
+ const ostring = () => stringType().optional();
11892
+ const onumber = () => numberType().optional();
11893
+ const oboolean = () => booleanType().optional();
11894
+ const coerce = {
11895
+ string: (arg) => ZodString.create({ ...arg, coerce: true }),
11896
+ number: (arg) => ZodNumber.create({ ...arg, coerce: true }),
11897
+ boolean: (arg) => ZodBoolean.create({
11898
+ ...arg,
11899
+ coerce: true
11900
+ }),
11901
+ bigint: (arg) => ZodBigInt.create({ ...arg, coerce: true }),
11902
+ date: (arg) => ZodDate.create({ ...arg, coerce: true })
11903
+ };
11904
+ const NEVER = INVALID;
11905
+ const z = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
11906
+ __proto__: null,
11907
+ BRAND,
11908
+ DIRTY,
11909
+ EMPTY_PATH,
11910
+ INVALID,
11911
+ NEVER,
11912
+ OK,
11913
+ ParseStatus,
11914
+ Schema: ZodType,
11915
+ ZodAny,
11916
+ ZodArray,
11917
+ ZodBigInt,
11918
+ ZodBoolean,
11919
+ ZodBranded,
11920
+ ZodCatch,
11921
+ ZodDate,
11922
+ ZodDefault,
11923
+ ZodDiscriminatedUnion,
11924
+ ZodEffects,
11925
+ ZodEnum,
11926
+ ZodError,
11927
+ get ZodFirstPartyTypeKind() {
11928
+ return ZodFirstPartyTypeKind;
11929
+ },
11930
+ ZodFunction,
11931
+ ZodIntersection,
11932
+ ZodIssueCode,
11933
+ ZodLazy,
11934
+ ZodLiteral,
11935
+ ZodMap,
11936
+ ZodNaN,
11937
+ ZodNativeEnum,
11938
+ ZodNever,
11939
+ ZodNull,
11940
+ ZodNullable,
11941
+ ZodNumber,
11942
+ ZodObject,
11943
+ ZodOptional,
11944
+ ZodParsedType,
11945
+ ZodPipeline,
11946
+ ZodPromise,
11947
+ ZodReadonly,
11948
+ ZodRecord,
11949
+ ZodSchema: ZodType,
11950
+ ZodSet,
11951
+ ZodString,
11952
+ ZodSymbol,
11953
+ ZodTransformer: ZodEffects,
11954
+ ZodTuple,
11955
+ ZodType,
11956
+ ZodUndefined,
11957
+ ZodUnion,
11958
+ ZodUnknown,
11959
+ ZodVoid,
11960
+ addIssueToContext,
11961
+ any: anyType,
11962
+ array: arrayType,
11963
+ bigint: bigIntType,
11964
+ boolean: booleanType,
11965
+ coerce,
11966
+ custom,
11967
+ date: dateType,
11968
+ datetimeRegex,
11969
+ defaultErrorMap: errorMap,
11970
+ discriminatedUnion: discriminatedUnionType,
11971
+ effect: effectsType,
11972
+ enum: enumType,
11973
+ function: functionType,
11974
+ getErrorMap,
11975
+ getParsedType,
11976
+ instanceof: instanceOfType,
11977
+ intersection: intersectionType,
11978
+ isAborted,
11979
+ isAsync,
11980
+ isDirty,
11981
+ isValid,
11982
+ late,
11983
+ lazy: lazyType,
11984
+ literal: literalType,
11985
+ makeIssue,
11986
+ map: mapType,
11987
+ nan: nanType,
11988
+ nativeEnum: nativeEnumType,
11989
+ never: neverType,
11990
+ null: nullType,
11991
+ nullable: nullableType,
11992
+ number: numberType,
11993
+ object: objectType,
11994
+ get objectUtil() {
11995
+ return objectUtil;
11996
+ },
11997
+ oboolean,
11998
+ onumber,
11999
+ optional: optionalType,
12000
+ ostring,
12001
+ pipeline: pipelineType,
12002
+ preprocess: preprocessType,
12003
+ promise: promiseType,
12004
+ quotelessJson,
12005
+ record: recordType,
12006
+ set: setType,
12007
+ setErrorMap,
12008
+ strictObject: strictObjectType,
12009
+ string: stringType,
12010
+ symbol: symbolType,
12011
+ transformer: effectsType,
12012
+ tuple: tupleType,
12013
+ undefined: undefinedType,
12014
+ union: unionType,
12015
+ unknown: unknownType,
12016
+ get util() {
12017
+ return util$3;
12018
+ },
12019
+ void: voidType
12020
+ }, Symbol.toStringTag, { value: "Module" }));
12021
+ const BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
12022
+ function base32Encode(buffer) {
12023
+ let bits = 0;
12024
+ let value = 0;
12025
+ let output = "";
12026
+ for (let i = 0; i < buffer.length; i++) {
12027
+ value = value << 8 | buffer[i];
12028
+ bits += 8;
12029
+ while (bits >= 5) {
12030
+ output += BASE32_ALPHABET[value >>> bits - 5 & 31];
12031
+ bits -= 5;
12032
+ }
12033
+ }
12034
+ if (bits > 0) {
12035
+ output += BASE32_ALPHABET[value << 5 - bits & 31];
12036
+ }
12037
+ return output;
12038
+ }
12039
+ function base32Decode(encoded) {
12040
+ const cleanInput = encoded.replace(/=+$/, "").toUpperCase();
12041
+ const bytes = [];
12042
+ let bits = 0;
12043
+ let value = 0;
12044
+ for (let i = 0; i < cleanInput.length; i++) {
12045
+ const index = BASE32_ALPHABET.indexOf(cleanInput[i]);
12046
+ if (index === -1) continue;
12047
+ value = value << 5 | index;
12048
+ bits += 5;
12049
+ if (bits >= 8) {
12050
+ bytes.push(value >>> bits - 8 & 255);
12051
+ bits -= 8;
12052
+ }
12053
+ }
12054
+ return Buffer.from(bytes);
12055
+ }
12056
+ function generateHotp(secret, counter) {
12057
+ const hmac = createHmac("sha1", secret);
12058
+ const counterBuffer = Buffer.alloc(8);
12059
+ counterBuffer.writeBigInt64BE(counter);
12060
+ hmac.update(counterBuffer);
12061
+ const hash = hmac.digest();
12062
+ const offset = hash[hash.length - 1] & 15;
12063
+ const code2 = ((hash[offset] & 127) << 24 | hash[offset + 1] << 16 | hash[offset + 2] << 8 | hash[offset + 3]) % 1e6;
12064
+ return code2.toString().padStart(6, "0");
12065
+ }
12066
+ function verifyTotp(secret, token, window2 = 1) {
12067
+ const timeStep = 30;
12068
+ const counter = BigInt(Math.floor(Date.now() / 1e3 / timeStep));
12069
+ for (let i = -window2; i <= window2; i++) {
12070
+ if (generateHotp(secret, counter + BigInt(i)) === token) {
12071
+ return true;
12072
+ }
12073
+ }
12074
+ return false;
12075
+ }
12076
+ function generateTotpSecret(issuer, accountName) {
12077
+ const secretBuffer = randomBytes(20);
12078
+ const secret = base32Encode(secretBuffer);
12079
+ const encodedIssuer = encodeURIComponent(issuer);
12080
+ const encodedAccount = encodeURIComponent(accountName);
12081
+ const uri = `otpauth://totp/${encodedIssuer}:${encodedAccount}?secret=${secret}&issuer=${encodedIssuer}&algorithm=SHA1&digits=6&period=30`;
12082
+ return {
12083
+ secret,
12084
+ uri
12085
+ };
12086
+ }
12087
+ function generateRecoveryCodes(count = 10) {
12088
+ return Array.from({
12089
+ length: count
12090
+ }, () => {
12091
+ const raw = randomBytes(5).toString("hex").toUpperCase();
12092
+ const parts = raw.match(/.{1,5}/g);
12093
+ return parts ? parts.join("-") : raw;
12094
+ });
12095
+ }
12096
+ function hashRecoveryCode(code2) {
12097
+ return createHash("sha256").update(code2.replace(/-/g, "").toUpperCase()).digest("hex");
12098
+ }
11200
12099
  function buildAuthResponse(user, roleIds, accessToken, refreshToken) {
11201
12100
  return {
11202
12101
  user: {
@@ -11234,9 +12133,9 @@ function createAuthRoutes(config) {
11234
12133
  emailService,
11235
12134
  emailConfig,
11236
12135
  allowRegistration = false,
11237
- overrides
12136
+ authHooks
11238
12137
  } = config;
11239
- const ops = resolveAuthOverrides(overrides);
12138
+ const ops = resolveAuthHooks(authHooks);
11240
12139
  const registerSchema = objectType({
11241
12140
  email: stringType().email("Invalid email address").max(255),
11242
12141
  password: stringType().min(1, "Password is required").max(128),
@@ -11300,7 +12199,19 @@ function createAuthRoutes(config) {
11300
12199
  async function createSessionAndTokens(userId, userAgent, ipAddress) {
11301
12200
  const roles = await authRepo.getUserRoles(userId);
11302
12201
  const roleIds = roles.map((r) => r.id);
11303
- const accessToken = generateAccessToken(userId, roleIds);
12202
+ let customClaims;
12203
+ if (authHooks?.customizeAccessToken) {
12204
+ const user = await authRepo.getUserById(userId);
12205
+ if (user) {
12206
+ const defaultClaims = {
12207
+ userId,
12208
+ roles: roleIds,
12209
+ aal: "aal1"
12210
+ };
12211
+ customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
12212
+ }
12213
+ }
12214
+ const accessToken = generateAccessToken(userId, roleIds, "aal1", customClaims);
11304
12215
  const refreshToken = generateRefreshToken();
11305
12216
  await authRepo.createRefreshToken(userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), userAgent, ipAddress);
11306
12217
  return {
@@ -11335,8 +12246,8 @@ function createAuthRoutes(config) {
11335
12246
  passwordHash,
11336
12247
  displayName: displayName || void 0
11337
12248
  };
11338
- if (overrides?.beforeUserCreate) {
11339
- createData = await overrides.beforeUserCreate(createData);
12249
+ if (authHooks?.beforeUserCreate) {
12250
+ createData = await authHooks.beforeUserCreate(createData);
11340
12251
  }
11341
12252
  const user = await authRepo.createUser(createData);
11342
12253
  const existingUsers = await authRepo.listUsers();
@@ -11355,14 +12266,16 @@ function createAuthRoutes(config) {
11355
12266
  email: user.email,
11356
12267
  displayName: user.displayName
11357
12268
  });
11358
- if (overrides?.afterUserCreate) {
11359
- overrides.afterUserCreate(user).catch((err) => {
11360
- console.error("[AuthOverrides] afterUserCreate error:", err instanceof Error ? err.message : err);
11361
- });
12269
+ if (authHooks?.afterUserCreate) {
12270
+ try {
12271
+ await authHooks.afterUserCreate(user);
12272
+ } catch (err) {
12273
+ console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
12274
+ }
11362
12275
  }
11363
- if (overrides?.onAuthenticated) {
11364
- overrides.onAuthenticated(user, "register").catch((err) => {
11365
- console.error("[AuthOverrides] onAuthenticated error:", err instanceof Error ? err.message : err);
12276
+ if (authHooks?.onAuthenticated) {
12277
+ authHooks.onAuthenticated(user, "register").catch((err) => {
12278
+ console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
11366
12279
  });
11367
12280
  }
11368
12281
  return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
@@ -11372,9 +12285,12 @@ function createAuthRoutes(config) {
11372
12285
  email,
11373
12286
  password
11374
12287
  } = parseBody2(loginSchema, await c.req.json());
12288
+ if (authHooks?.beforeLogin) {
12289
+ await authHooks.beforeLogin(email, "login");
12290
+ }
11375
12291
  let user;
11376
- if (overrides?.verifyCredentials) {
11377
- user = await overrides.verifyCredentials(email, password, authRepo);
12292
+ if (authHooks?.verifyCredentials) {
12293
+ user = await authHooks.verifyCredentials(email, password, authRepo);
11378
12294
  if (!user) {
11379
12295
  throw ApiError.unauthorized("Invalid email or password", "INVALID_CREDENTIALS");
11380
12296
  }
@@ -11396,9 +12312,9 @@ function createAuthRoutes(config) {
11396
12312
  accessToken,
11397
12313
  refreshToken
11398
12314
  } = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
11399
- if (overrides?.onAuthenticated) {
11400
- overrides.onAuthenticated(user, "login").catch((err) => {
11401
- console.error("[AuthOverrides] onAuthenticated error:", err instanceof Error ? err.message : err);
12315
+ if (authHooks?.onAuthenticated) {
12316
+ authHooks.onAuthenticated(user, "login").catch((err) => {
12317
+ console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
11402
12318
  });
11403
12319
  }
11404
12320
  return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken));
@@ -11437,6 +12353,13 @@ function createAuthRoutes(config) {
11437
12353
  await authRepo.linkUserIdentity(user.id, provider.id, externalUser.providerId, {
11438
12354
  email: externalUser.email
11439
12355
  });
12356
+ if (authHooks?.afterUserCreate) {
12357
+ try {
12358
+ await authHooks.afterUserCreate(user);
12359
+ } catch (err) {
12360
+ console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
12361
+ }
12362
+ }
11440
12363
  const allUsers = await authRepo.listUsers();
11441
12364
  const isFirstUser = allUsers.length === 1 && allUsers[0].id === user.id;
11442
12365
  if (isFirstUser) {
@@ -11522,6 +12445,11 @@ function createAuthRoutes(config) {
11522
12445
  await authRepo.updatePassword(storedToken.userId, passwordHash);
11523
12446
  await authRepo.markPasswordResetTokenUsed(tokenHash);
11524
12447
  await authRepo.deleteAllRefreshTokensForUser(storedToken.userId);
12448
+ if (authHooks?.onPasswordReset) {
12449
+ authHooks.onPasswordReset(storedToken.userId).catch((err) => {
12450
+ console.error("[AuthHooks] onPasswordReset error:", err instanceof Error ? err.message : err);
12451
+ });
12452
+ }
11525
12453
  return c.json({
11526
12454
  success: true,
11527
12455
  message: "Password has been reset successfully"
@@ -11625,7 +12553,19 @@ function createAuthRoutes(config) {
11625
12553
  }
11626
12554
  const roles = await authRepo.getUserRoles(storedToken.userId);
11627
12555
  const roleIds = roles.map((r) => r.id);
11628
- const newAccessToken = generateAccessToken(storedToken.userId, roleIds);
12556
+ let customClaims;
12557
+ if (authHooks?.customizeAccessToken) {
12558
+ const user = await authRepo.getUserById(storedToken.userId);
12559
+ if (user) {
12560
+ const defaultClaims = {
12561
+ userId: storedToken.userId,
12562
+ roles: roleIds,
12563
+ aal: "aal1"
12564
+ };
12565
+ customClaims = await authHooks.customizeAccessToken(defaultClaims, user);
12566
+ }
12567
+ }
12568
+ const newAccessToken = generateAccessToken(storedToken.userId, roleIds, "aal1", customClaims);
11629
12569
  const newRefreshToken = generateRefreshToken();
11630
12570
  const userAgent = c.req.header("user-agent") || "unknown";
11631
12571
  const ipAddress = c.req.header("x-forwarded-for") || "unknown";
@@ -11647,6 +12587,18 @@ function createAuthRoutes(config) {
11647
12587
  const tokenHash = hashRefreshToken(refreshToken);
11648
12588
  await authRepo.deleteRefreshToken(tokenHash);
11649
12589
  }
12590
+ const authHeader = c.req.header("authorization");
12591
+ if (authHooks?.afterLogout && authHeader?.startsWith("Bearer ")) {
12592
+ const {
12593
+ verifyAccessToken: verifyAccessToken2
12594
+ } = await Promise.resolve().then(() => jwt);
12595
+ const payload = verifyAccessToken2(authHeader.substring(7));
12596
+ if (payload) {
12597
+ authHooks.afterLogout(payload.userId).catch((err) => {
12598
+ console.error("[AuthHooks] afterLogout error:", err instanceof Error ? err.message : err);
12599
+ });
12600
+ }
12601
+ }
11650
12602
  return c.json({
11651
12603
  success: true
11652
12604
  });
@@ -11766,6 +12718,270 @@ function createAuthRoutes(config) {
11766
12718
  enabledProviders
11767
12719
  });
11768
12720
  });
12721
+ router.post("/anonymous", strictAuthLimiter, async (c) => {
12722
+ const anonId = randomBytes(16).toString("hex");
12723
+ const anonEmail = `anon_${anonId.slice(0, 8)}@anonymous.local`;
12724
+ let createData = {
12725
+ email: anonEmail,
12726
+ emailVerified: false,
12727
+ isAnonymous: true
12728
+ };
12729
+ if (authHooks?.beforeUserCreate) {
12730
+ createData = await authHooks.beforeUserCreate(createData);
12731
+ }
12732
+ const user = await authRepo.createUser(createData);
12733
+ if (config.defaultRole) {
12734
+ await authRepo.assignDefaultRole(user.id, config.defaultRole);
12735
+ }
12736
+ const {
12737
+ roleIds,
12738
+ accessToken,
12739
+ refreshToken
12740
+ } = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
12741
+ if (authHooks?.afterUserCreate) {
12742
+ authHooks.afterUserCreate(user).catch((err) => {
12743
+ console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
12744
+ });
12745
+ }
12746
+ if (authHooks?.onAuthenticated) {
12747
+ authHooks.onAuthenticated(user, "anonymous").catch((err) => {
12748
+ console.error("[AuthHooks] onAuthenticated error:", err instanceof Error ? err.message : err);
12749
+ });
12750
+ }
12751
+ return c.json(buildAuthResponse(user, roleIds, accessToken, refreshToken), 201);
12752
+ });
12753
+ router.post("/anonymous/link", requireAuth, async (c) => {
12754
+ const userCtx = c.get("user");
12755
+ if (!userCtx) {
12756
+ throw ApiError.unauthorized("Not authenticated");
12757
+ }
12758
+ const user = await authRepo.getUserById(userCtx.userId);
12759
+ if (!user?.isAnonymous) {
12760
+ throw ApiError.badRequest("User is not anonymous", "NOT_ANONYMOUS");
12761
+ }
12762
+ const linkSchema = objectType({
12763
+ email: stringType().email("Invalid email address").max(255),
12764
+ password: stringType().min(1, "Password is required").max(128)
12765
+ });
12766
+ const {
12767
+ email,
12768
+ password
12769
+ } = parseBody2(linkSchema, await c.req.json());
12770
+ const passwordValidation = ops.validatePasswordStrength(password);
12771
+ if (!passwordValidation.valid) {
12772
+ throw ApiError.badRequest(passwordValidation.errors.join(". "), "WEAK_PASSWORD");
12773
+ }
12774
+ const existingUser = await authRepo.getUserByEmail(email.toLowerCase());
12775
+ if (existingUser) {
12776
+ throw ApiError.conflict("Email already registered", "EMAIL_EXISTS");
12777
+ }
12778
+ const passwordHash = await ops.hashPassword(password);
12779
+ const updatedUser = await authRepo.updateUser(user.id, {
12780
+ email: email.toLowerCase(),
12781
+ passwordHash,
12782
+ isAnonymous: false
12783
+ });
12784
+ if (!updatedUser) {
12785
+ throw ApiError.notFound("User not found");
12786
+ }
12787
+ const {
12788
+ roleIds,
12789
+ accessToken,
12790
+ refreshToken
12791
+ } = await createSessionAndTokens(user.id, c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
12792
+ return c.json(buildAuthResponse(updatedUser, roleIds, accessToken, refreshToken));
12793
+ });
12794
+ router.post("/mfa/enroll", requireAuth, async (c) => {
12795
+ const userCtx = c.get("user");
12796
+ if (!userCtx) {
12797
+ throw ApiError.unauthorized("Not authenticated");
12798
+ }
12799
+ const body = await c.req.json().catch(() => ({}));
12800
+ const friendlyName = typeof body.friendlyName === "string" ? body.friendlyName : void 0;
12801
+ const issuer = typeof body.issuer === "string" ? body.issuer : emailConfig?.appName || "Rebase";
12802
+ const user = await authRepo.getUserById(userCtx.userId);
12803
+ if (!user) {
12804
+ throw ApiError.notFound("User not found");
12805
+ }
12806
+ const {
12807
+ secret,
12808
+ uri
12809
+ } = generateTotpSecret(issuer, user.email);
12810
+ const factor = await authRepo.createMfaFactor(
12811
+ user.id,
12812
+ "totp",
12813
+ secret,
12814
+ // In production, encrypt this before storage
12815
+ friendlyName
12816
+ );
12817
+ const codes = generateRecoveryCodes(10);
12818
+ const codeHashes = codes.map(hashRecoveryCode);
12819
+ await authRepo.createRecoveryCodes(user.id, codeHashes);
12820
+ return c.json({
12821
+ factor: {
12822
+ id: factor.id,
12823
+ factorType: factor.factorType,
12824
+ friendlyName: factor.friendlyName
12825
+ },
12826
+ totp: {
12827
+ secret,
12828
+ uri,
12829
+ qrUri: uri
12830
+ // Client can use a QR library to render this
12831
+ },
12832
+ recoveryCodes: codes
12833
+ }, 201);
12834
+ });
12835
+ router.post("/mfa/verify", requireAuth, async (c) => {
12836
+ const userCtx = c.get("user");
12837
+ if (!userCtx) {
12838
+ throw ApiError.unauthorized("Not authenticated");
12839
+ }
12840
+ const verifySchema = objectType({
12841
+ factorId: stringType().min(1, "Factor ID is required"),
12842
+ code: stringType().length(6, "Code must be 6 digits")
12843
+ });
12844
+ const {
12845
+ factorId,
12846
+ code: code2
12847
+ } = parseBody2(verifySchema, await c.req.json());
12848
+ const factor = await authRepo.getMfaFactorById(factorId);
12849
+ if (!factor || factor.userId !== userCtx.userId) {
12850
+ throw ApiError.notFound("MFA factor not found");
12851
+ }
12852
+ if (factor.verified) {
12853
+ throw ApiError.badRequest("Factor is already verified", "ALREADY_VERIFIED");
12854
+ }
12855
+ const secretBuffer = base32Decode(factor.secretEncrypted);
12856
+ const isValid2 = verifyTotp(secretBuffer, code2);
12857
+ if (!isValid2) {
12858
+ throw ApiError.unauthorized("Invalid TOTP code", "INVALID_CODE");
12859
+ }
12860
+ await authRepo.verifyMfaFactor(factorId);
12861
+ return c.json({
12862
+ success: true,
12863
+ message: "MFA factor verified and enrolled"
12864
+ });
12865
+ });
12866
+ router.post("/mfa/challenge", requireAuth, async (c) => {
12867
+ const userCtx = c.get("user");
12868
+ if (!userCtx) {
12869
+ throw ApiError.unauthorized("Not authenticated");
12870
+ }
12871
+ const challengeSchema = objectType({
12872
+ factorId: stringType().min(1, "Factor ID is required")
12873
+ });
12874
+ const {
12875
+ factorId
12876
+ } = parseBody2(challengeSchema, await c.req.json());
12877
+ const factor = await authRepo.getMfaFactorById(factorId);
12878
+ if (!factor || factor.userId !== userCtx.userId) {
12879
+ throw ApiError.notFound("MFA factor not found");
12880
+ }
12881
+ if (!factor.verified) {
12882
+ throw ApiError.badRequest("MFA factor is not yet verified", "FACTOR_NOT_VERIFIED");
12883
+ }
12884
+ const ipAddress = c.req.header("x-forwarded-for") || "unknown";
12885
+ const challenge = await authRepo.createMfaChallenge(factorId, ipAddress);
12886
+ return c.json({
12887
+ challengeId: challenge.id,
12888
+ factorId: challenge.factorId,
12889
+ expiresAt: new Date(Date.now() + 5 * 60 * 1e3).toISOString()
12890
+ });
12891
+ });
12892
+ router.post("/mfa/challenge/verify", requireAuth, async (c) => {
12893
+ const userCtx = c.get("user");
12894
+ if (!userCtx) {
12895
+ throw ApiError.unauthorized("Not authenticated");
12896
+ }
12897
+ const challengeVerifySchema = objectType({
12898
+ challengeId: stringType().min(1, "Challenge ID is required"),
12899
+ code: stringType().min(1, "Code is required")
12900
+ });
12901
+ const {
12902
+ challengeId,
12903
+ code: code2
12904
+ } = parseBody2(challengeVerifySchema, await c.req.json());
12905
+ const challenge = await authRepo.getMfaChallengeById(challengeId);
12906
+ if (!challenge) {
12907
+ throw ApiError.badRequest("Invalid or expired challenge", "INVALID_CHALLENGE");
12908
+ }
12909
+ const factor = await authRepo.getMfaFactorById(challenge.factorId);
12910
+ if (!factor || factor.userId !== userCtx.userId) {
12911
+ throw ApiError.notFound("MFA factor not found");
12912
+ }
12913
+ const isRecoveryCode = code2.length > 6;
12914
+ let isValid2 = false;
12915
+ if (isRecoveryCode) {
12916
+ const codeHash = hashRecoveryCode(code2);
12917
+ isValid2 = await authRepo.useRecoveryCode(userCtx.userId, codeHash);
12918
+ } else {
12919
+ const secretBuffer = base32Decode(factor.secretEncrypted);
12920
+ isValid2 = verifyTotp(secretBuffer, code2);
12921
+ }
12922
+ if (!isValid2) {
12923
+ throw ApiError.unauthorized("Invalid verification code", "INVALID_CODE");
12924
+ }
12925
+ await authRepo.verifyMfaChallenge(challengeId);
12926
+ const roles = await authRepo.getUserRoles(userCtx.userId);
12927
+ const roleIds = roles.map((r) => r.id);
12928
+ const accessToken = generateAccessToken(userCtx.userId, roleIds, "aal2");
12929
+ const refreshToken = generateRefreshToken();
12930
+ await authRepo.createRefreshToken(userCtx.userId, hashRefreshToken(refreshToken), getRefreshTokenExpiry(), c.req.header("user-agent") || "unknown", c.req.header("x-forwarded-for") || "unknown");
12931
+ if (authHooks?.onMfaVerified) {
12932
+ authHooks.onMfaVerified(userCtx.userId, factor.id).catch((err) => {
12933
+ console.error("[AuthHooks] onMfaVerified error:", err instanceof Error ? err.message : err);
12934
+ });
12935
+ }
12936
+ return c.json({
12937
+ tokens: {
12938
+ accessToken,
12939
+ refreshToken,
12940
+ accessTokenExpiresAt: getAccessTokenExpiry()
12941
+ }
12942
+ });
12943
+ });
12944
+ router.get("/mfa/factors", requireAuth, async (c) => {
12945
+ const userCtx = c.get("user");
12946
+ if (!userCtx) {
12947
+ throw ApiError.unauthorized("Not authenticated");
12948
+ }
12949
+ const factors = await authRepo.getMfaFactors(userCtx.userId);
12950
+ return c.json({
12951
+ factors: factors.map((f2) => ({
12952
+ id: f2.id,
12953
+ factorType: f2.factorType,
12954
+ friendlyName: f2.friendlyName,
12955
+ verified: f2.verified,
12956
+ createdAt: f2.createdAt
12957
+ }))
12958
+ });
12959
+ });
12960
+ router.delete("/mfa/unenroll", requireAuth, async (c) => {
12961
+ const userCtx = c.get("user");
12962
+ if (!userCtx) {
12963
+ throw ApiError.unauthorized("Not authenticated");
12964
+ }
12965
+ const unenrollSchema = objectType({
12966
+ factorId: stringType().min(1, "Factor ID is required")
12967
+ });
12968
+ const {
12969
+ factorId
12970
+ } = parseBody2(unenrollSchema, await c.req.json());
12971
+ const factor = await authRepo.getMfaFactorById(factorId);
12972
+ if (!factor || factor.userId !== userCtx.userId) {
12973
+ throw ApiError.notFound("MFA factor not found");
12974
+ }
12975
+ await authRepo.deleteMfaFactor(factorId, userCtx.userId);
12976
+ const hasFactors = await authRepo.hasVerifiedMfaFactors(userCtx.userId);
12977
+ if (!hasFactors) {
12978
+ await authRepo.deleteAllRecoveryCodes(userCtx.userId);
12979
+ }
12980
+ return c.json({
12981
+ success: true,
12982
+ message: "MFA factor removed"
12983
+ });
12984
+ });
11769
12985
  return router;
11770
12986
  }
11771
12987
  function generateSecurePassword() {
@@ -11797,9 +13013,9 @@ function createAdminRoutes(config) {
11797
13013
  emailService,
11798
13014
  emailConfig,
11799
13015
  hooks,
11800
- overrides
13016
+ authHooks
11801
13017
  } = config;
11802
- const ops = resolveAuthOverrides(overrides);
13018
+ const ops = resolveAuthHooks(authHooks);
11803
13019
  function buildHookContext(c, method) {
11804
13020
  const user = c.get("user");
11805
13021
  return {
@@ -11867,6 +13083,10 @@ function createAdminRoutes(config) {
11867
13083
  if (!userId) {
11868
13084
  throw ApiError.unauthorized("User ID not found in auth context");
11869
13085
  }
13086
+ const caller = await authRepo.getUserById(userId);
13087
+ if (!caller) {
13088
+ throw ApiError.notFound("Authenticated user does not exist in the database. Please sign out and sign in/register again.", "USER_NOT_FOUND");
13089
+ }
11870
13090
  await authRepo.setUserRoles(userId, ["admin"]);
11871
13091
  if (config.setBootstrapCompleted) {
11872
13092
  await config.setBootstrapCompleted();
@@ -12118,6 +13338,18 @@ function createAdminRoutes(config) {
12118
13338
  await authRepo.updateUser(userId, updates);
12119
13339
  }
12120
13340
  if (roles !== void 0 && Array.isArray(roles)) {
13341
+ const currentRoles = await authRepo.getUserRoleIds(userId);
13342
+ const wasAdmin = currentRoles.includes("admin");
13343
+ const willBeAdmin = roles.includes("admin");
13344
+ if (wasAdmin && !willBeAdmin) {
13345
+ const adminUsers = await authRepo.listUsersPaginated({
13346
+ roleId: "admin",
13347
+ limit: 1
13348
+ });
13349
+ if (adminUsers.total <= 1) {
13350
+ throw ApiError.forbidden("Cannot demote the last administrator", "LAST_ADMIN");
13351
+ }
13352
+ }
12121
13353
  await authRepo.setUserRoles(userId, roles);
12122
13354
  }
12123
13355
  const result = await authRepo.getUserWithRoles(userId);
@@ -12142,6 +13374,16 @@ function createAdminRoutes(config) {
12142
13374
  if (!existing) {
12143
13375
  throw ApiError.notFound("User not found");
12144
13376
  }
13377
+ const roles = await authRepo.getUserRoleIds(userId);
13378
+ if (roles.includes("admin")) {
13379
+ const adminUsers = await authRepo.listUsersPaginated({
13380
+ roleId: "admin",
13381
+ limit: 1
13382
+ });
13383
+ if (adminUsers.total <= 1) {
13384
+ throw ApiError.forbidden("Cannot delete the last administrator", "LAST_ADMIN");
13385
+ }
13386
+ }
12145
13387
  const hookCtx = buildHookContext(c, "DELETE");
12146
13388
  if (hooks?.users?.beforeDelete) {
12147
13389
  await hooks.users.beforeDelete(userId, hookCtx);
@@ -12163,8 +13405,7 @@ function createAdminRoutes(config) {
12163
13405
  id: r.id,
12164
13406
  name: r.name,
12165
13407
  isAdmin: r.isAdmin,
12166
- defaultPermissions: r.defaultPermissions,
12167
- config: r.config
13408
+ defaultPermissions: r.defaultPermissions
12168
13409
  }));
12169
13410
  adminRoles = await applyRoleAfterReadBatch(adminRoles, hookCtx);
12170
13411
  return c.json({
@@ -12187,8 +13428,7 @@ function createAdminRoutes(config) {
12187
13428
  id,
12188
13429
  name: name2,
12189
13430
  isAdmin,
12190
- defaultPermissions,
12191
- config: config2
13431
+ defaultPermissions
12192
13432
  } = body;
12193
13433
  if (!id || !name2) {
12194
13434
  throw ApiError.badRequest("Role ID and name are required", "INVALID_INPUT");
@@ -12201,8 +13441,7 @@ function createAdminRoutes(config) {
12201
13441
  id,
12202
13442
  name: name2,
12203
13443
  isAdmin: isAdmin ?? false,
12204
- defaultPermissions: defaultPermissions ?? null,
12205
- config: config2 ?? null
13444
+ defaultPermissions: defaultPermissions ?? null
12206
13445
  });
12207
13446
  return c.json({
12208
13447
  role
@@ -12214,8 +13453,7 @@ function createAdminRoutes(config) {
12214
13453
  const {
12215
13454
  name: name2,
12216
13455
  isAdmin,
12217
- defaultPermissions,
12218
- config: config2
13456
+ defaultPermissions
12219
13457
  } = body;
12220
13458
  const existing = await authRepo.getRoleById(roleId);
12221
13459
  if (!existing) {
@@ -12224,8 +13462,7 @@ function createAdminRoutes(config) {
12224
13462
  const role = await authRepo.updateRole(roleId, {
12225
13463
  name: name2,
12226
13464
  isAdmin,
12227
- defaultPermissions,
12228
- config: config2
13465
+ defaultPermissions
12229
13466
  });
12230
13467
  return c.json({
12231
13468
  role
@@ -12257,9 +13494,9 @@ function createBuiltinAuthAdapter(config) {
12257
13494
  oauthProviders = [],
12258
13495
  serviceKey,
12259
13496
  hooks,
12260
- overrides
13497
+ authHooks
12261
13498
  } = config;
12262
- const resolvedOps = resolveAuthOverrides(overrides);
13499
+ const resolvedOps = resolveAuthHooks(authHooks);
12263
13500
  const adapter = {
12264
13501
  id: "rebase-builtin",
12265
13502
  serviceKey,
@@ -12333,7 +13570,7 @@ function createBuiltinAuthAdapter(config) {
12333
13570
  rawToken: token
12334
13571
  };
12335
13572
  },
12336
- userManagement: createUserManagementFromRepo(authRepository, resolvedOps, overrides),
13573
+ userManagement: createUserManagementFromRepo(authRepository, resolvedOps, authHooks),
12337
13574
  roleManagement: createRoleManagementFromRepo(authRepository),
12338
13575
  createAuthRoutes() {
12339
13576
  return createAuthRoutes({
@@ -12343,7 +13580,7 @@ function createBuiltinAuthAdapter(config) {
12343
13580
  allowRegistration,
12344
13581
  defaultRole,
12345
13582
  oauthProviders,
12346
- overrides
13583
+ authHooks
12347
13584
  });
12348
13585
  },
12349
13586
  createAdminRoutes() {
@@ -12353,7 +13590,7 @@ function createBuiltinAuthAdapter(config) {
12353
13590
  emailConfig,
12354
13591
  serviceKey,
12355
13592
  hooks,
12356
- overrides
13593
+ authHooks
12357
13594
  });
12358
13595
  },
12359
13596
  async getCapabilities() {
@@ -12382,7 +13619,7 @@ function createBuiltinAuthAdapter(config) {
12382
13619
  };
12383
13620
  return adapter;
12384
13621
  }
12385
- function createUserManagementFromRepo(repo, resolvedOps, overrides) {
13622
+ function createUserManagementFromRepo(repo, resolvedOps, authHooks) {
12386
13623
  return {
12387
13624
  async listUsers(options2) {
12388
13625
  const result = await repo.listUsersPaginated({
@@ -12413,14 +13650,16 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
12413
13650
  photoUrl: data.photoUrl,
12414
13651
  metadata: data.metadata
12415
13652
  };
12416
- if (overrides?.beforeUserCreate) {
12417
- createData = await overrides.beforeUserCreate(createData);
13653
+ if (authHooks?.beforeUserCreate) {
13654
+ createData = await authHooks.beforeUserCreate(createData);
12418
13655
  }
12419
13656
  const user = await repo.createUser(createData);
12420
- if (overrides?.afterUserCreate) {
12421
- overrides.afterUserCreate(user).catch((err) => {
12422
- console.error("[AuthOverrides] afterUserCreate error:", err instanceof Error ? err.message : err);
12423
- });
13657
+ if (authHooks?.afterUserCreate) {
13658
+ try {
13659
+ await authHooks.afterUserCreate(user);
13660
+ } catch (err) {
13661
+ console.error("[AuthHooks] afterUserCreate error:", err instanceof Error ? err.message : err);
13662
+ }
12424
13663
  }
12425
13664
  return toAuthUserData(user);
12426
13665
  },
@@ -12437,7 +13676,15 @@ function createUserManagementFromRepo(repo, resolvedOps, overrides) {
12437
13676
  return user ? toAuthUserData(user) : null;
12438
13677
  },
12439
13678
  async deleteUser(id) {
13679
+ if (authHooks?.beforeUserDelete) {
13680
+ await authHooks.beforeUserDelete(id);
13681
+ }
12440
13682
  await repo.deleteUser(id);
13683
+ if (authHooks?.afterUserDelete) {
13684
+ authHooks.afterUserDelete(id).catch((err) => {
13685
+ console.error("[AuthHooks] afterUserDelete error:", err instanceof Error ? err.message : err);
13686
+ });
13687
+ }
12441
13688
  },
12442
13689
  async getUserRoles(userId) {
12443
13690
  const roles = await repo.getUserRoles(userId);
@@ -12464,8 +13711,7 @@ function createRoleManagementFromRepo(repo) {
12464
13711
  name: data.name,
12465
13712
  isAdmin: data.isAdmin,
12466
13713
  defaultPermissions: data.defaultPermissions,
12467
- collectionPermissions: data.collectionPermissions,
12468
- config: data.config
13714
+ collectionPermissions: data.collectionPermissions
12469
13715
  });
12470
13716
  return toAuthRoleData(role);
12471
13717
  },
@@ -12496,8 +13742,7 @@ function toAuthRoleData(role) {
12496
13742
  name: role.name,
12497
13743
  isAdmin: role.isAdmin,
12498
13744
  defaultPermissions: role.defaultPermissions,
12499
- collectionPermissions: role.collectionPermissions,
12500
- config: role.config
13745
+ collectionPermissions: role.collectionPermissions
12501
13746
  };
12502
13747
  }
12503
13748
  function configureLogLevel(logLevel) {
@@ -12518,20 +13763,20 @@ function configureLogLevel(logLevel) {
12518
13763
  if (currentLevel < 0) console.error = () => {
12519
13764
  };
12520
13765
  }
13766
+ let originalConsole;
12521
13767
  function resetConsole() {
12522
- if (!global.__originalConsole) {
12523
- global.__originalConsole = {
13768
+ if (!originalConsole) {
13769
+ originalConsole = {
12524
13770
  log: console.log,
12525
13771
  warn: console.warn,
12526
13772
  error: console.error,
12527
13773
  debug: console.debug
12528
13774
  };
12529
13775
  }
12530
- const original = global.__originalConsole;
12531
- console.log = original.log;
12532
- console.warn = original.warn;
12533
- console.error = original.error;
12534
- console.debug = original.debug;
13776
+ console.log = originalConsole.log;
13777
+ console.warn = originalConsole.warn;
13778
+ console.error = originalConsole.error;
13779
+ console.debug = originalConsole.debug;
12535
13780
  }
12536
13781
  const GCP_SEVERITY = {
12537
13782
  debug: "DEBUG",
@@ -12773,12 +14018,6 @@ var browser$1 = { exports: {} };
12773
14018
  exports.Response = globalObject.Response;
12774
14019
  })(browser$1, browser$1.exports);
12775
14020
  var browserExports = browser$1.exports;
12776
- const __viteBrowserExternal = {};
12777
- const __viteBrowserExternal$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({
12778
- __proto__: null,
12779
- default: __viteBrowserExternal
12780
- }, Symbol.toStringTag, { value: "Module" }));
12781
- const require$$11 = /* @__PURE__ */ getAugmentedNamespace(__viteBrowserExternal$1);
12782
14021
  const isStream = (stream2) => stream2 !== null && typeof stream2 === "object" && typeof stream2.pipe === "function";
12783
14022
  isStream.writable = (stream2) => isStream(stream2) && stream2.writable !== false && typeof stream2._write === "function" && typeof stream2._writableState === "object";
12784
14023
  isStream.readable = (stream2) => isStream(stream2) && stream2.readable !== false && typeof stream2._read === "function" && typeof stream2._readableState === "object";
@@ -13563,16 +14802,16 @@ Object.defineProperty(sha1$1, "__esModule", {
13563
14802
  value: true
13564
14803
  });
13565
14804
  sha1$1.default = void 0;
13566
- function f(s2, x, y2, z) {
14805
+ function f(s2, x, y2, z2) {
13567
14806
  switch (s2) {
13568
14807
  case 0:
13569
- return x & y2 ^ ~x & z;
14808
+ return x & y2 ^ ~x & z2;
13570
14809
  case 1:
13571
- return x ^ y2 ^ z;
14810
+ return x ^ y2 ^ z2;
13572
14811
  case 2:
13573
- return x & y2 ^ x & z ^ y2 & z;
14812
+ return x & y2 ^ x & z2 ^ y2 & z2;
13574
14813
  case 3:
13575
- return x ^ y2 ^ z;
14814
+ return x ^ y2 ^ z2;
13576
14815
  }
13577
14816
  }
13578
14817
  function ROTL(x, n) {
@@ -14621,7 +15860,7 @@ gaxios.Gaxios = void 0;
14621
15860
  const extend_1 = __importDefault(extend);
14622
15861
  const https_1 = require$$1;
14623
15862
  const node_fetch_1 = __importDefault(browserExports);
14624
- const querystring_1$1 = __importDefault(require$$11);
15863
+ const querystring_1$1 = __importDefault(require$$1$2);
14625
15864
  const is_stream_1 = __importDefault(isStream_1);
14626
15865
  const url_1 = require$$0$5;
14627
15866
  const common_1 = common$1;
@@ -16299,11 +17538,11 @@ var bignumber = { exports: {} };
16299
17538
  return n > 0 || n === i ? i : i - 1;
16300
17539
  }
16301
17540
  function coeffToString(a2) {
16302
- var s2, z, i = 1, j = a2.length, r = a2[0] + "";
17541
+ var s2, z2, i = 1, j = a2.length, r = a2[0] + "";
16303
17542
  for (; i < j; ) {
16304
17543
  s2 = a2[i++] + "";
16305
- z = LOG_BASE - s2.length;
16306
- for (; z--; s2 = "0" + s2) ;
17544
+ z2 = LOG_BASE - s2.length;
17545
+ for (; z2--; s2 = "0" + s2) ;
16307
17546
  r += s2;
16308
17547
  }
16309
17548
  for (j = r.length; r.charCodeAt(--j) === 48; ) ;
@@ -16336,15 +17575,15 @@ var bignumber = { exports: {} };
16336
17575
  function toExponential(str, e) {
16337
17576
  return (str.length > 1 ? str.charAt(0) + "." + str.slice(1) : str) + (e < 0 ? "e" : "e+") + e;
16338
17577
  }
16339
- function toFixedPoint(str, e, z) {
17578
+ function toFixedPoint(str, e, z2) {
16340
17579
  var len, zs;
16341
17580
  if (e < 0) {
16342
- for (zs = z + "."; ++e; zs += z) ;
17581
+ for (zs = z2 + "."; ++e; zs += z2) ;
16343
17582
  str = zs + str;
16344
17583
  } else {
16345
17584
  len = str.length;
16346
17585
  if (++e > len) {
16347
- for (zs = z, e -= len; --e; zs += z) ;
17586
+ for (zs = z2, e -= len; --e; zs += z2) ;
16348
17587
  str += zs;
16349
17588
  } else if (e < len) {
16350
17589
  str = str.slice(0, e) + "." + str.slice(e);
@@ -16763,7 +18002,7 @@ var gcpResidency = {};
16763
18002
  exports.isGoogleComputeEngine = isGoogleComputeEngine;
16764
18003
  exports.detectGCPResidency = detectGCPResidency;
16765
18004
  const fs_1 = fs__default;
16766
- const os_1 = require$$1$2;
18005
+ const os_1 = require$$1$3;
16767
18006
  exports.GCE_LINUX_BIOS_PATHS = {
16768
18007
  BIOS_DATE: "/sys/class/dmi/id/bios_date",
16769
18008
  BIOS_VENDOR: "/sys/class/dmi/id/bios_vendor"
@@ -16896,7 +18135,7 @@ Colours.refresh();
16896
18135
  exports.setBackend = setBackend;
16897
18136
  exports.log = log;
16898
18137
  const node_events_1 = require$$0$8;
16899
- const process2 = __importStar2(require$$1$3);
18138
+ const process2 = __importStar2(require$$1$4);
16900
18139
  const util2 = __importStar2(require$$2$1);
16901
18140
  const colours_1 = colours;
16902
18141
  var LogSeverity;
@@ -17968,7 +19207,7 @@ loginticket.LoginTicket = LoginTicket;
17968
19207
  Object.defineProperty(oauth2client, "__esModule", { value: true });
17969
19208
  oauth2client.OAuth2Client = oauth2client.ClientAuthentication = oauth2client.CertificateFormat = oauth2client.CodeChallengeMethod = void 0;
17970
19209
  const gaxios_1$5 = src$2;
17971
- const querystring$2 = require$$11;
19210
+ const querystring$2 = require$$1$2;
17972
19211
  const stream$4 = require$$0$4;
17973
19212
  const formatEcdsa = ecdsaSigFormatter;
17974
19213
  const crypto_1$2 = requireCrypto();
@@ -19456,7 +20695,7 @@ var refreshclient = {};
19456
20695
  Object.defineProperty(refreshclient, "__esModule", { value: true });
19457
20696
  refreshclient.UserRefreshClient = refreshclient.USER_REFRESH_ACCOUNT_TYPE = void 0;
19458
20697
  const oauth2client_1$1 = oauth2client;
19459
- const querystring_1 = require$$11;
20698
+ const querystring_1 = require$$1$2;
19460
20699
  refreshclient.USER_REFRESH_ACCOUNT_TYPE = "authorized_user";
19461
20700
  class UserRefreshClient extends oauth2client_1$1.OAuth2Client {
19462
20701
  constructor(optionsOrClientId, clientSecret, refreshToken, eagerRefreshThresholdMillis, forceRefreshOnFailure) {
@@ -19731,7 +20970,7 @@ var oauth2common = {};
19731
20970
  Object.defineProperty(oauth2common, "__esModule", { value: true });
19732
20971
  oauth2common.OAuthClientAuthHandler = void 0;
19733
20972
  oauth2common.getErrorFromOAuthErrorResponse = getErrorFromOAuthErrorResponse;
19734
- const querystring$1 = require$$11;
20973
+ const querystring$1 = require$$1$2;
19735
20974
  const crypto_1$1 = requireCrypto();
19736
20975
  const METHODS_SUPPORTING_REQUEST_BODY = ["PUT", "POST", "PATCH"];
19737
20976
  class OAuthClientAuthHandler {
@@ -19877,7 +21116,7 @@ function getErrorFromOAuthErrorResponse(resp, err) {
19877
21116
  Object.defineProperty(stscredentials, "__esModule", { value: true });
19878
21117
  stscredentials.StsCredentials = void 0;
19879
21118
  const gaxios_1$1 = src$2;
19880
- const querystring = require$$11;
21119
+ const querystring = require$$1$2;
19881
21120
  const transporters_1 = transporters;
19882
21121
  const oauth2common_1$1 = oauth2common;
19883
21122
  class StsCredentials extends oauth2common_1$1.OAuthClientAuthHandler {
@@ -21483,7 +22722,7 @@ externalAccountAuthorizedUserClient.ExternalAccountAuthorizedUserClient = Extern
21483
22722
  const child_process_1 = require$$0$a;
21484
22723
  const fs2 = fs__default;
21485
22724
  const gcpMetadata2 = src$3;
21486
- const os2 = require$$1$2;
22725
+ const os2 = require$$1$3;
21487
22726
  const path2 = path__default;
21488
22727
  const crypto_12 = requireCrypto();
21489
22728
  const transporters_12 = transporters;
@@ -22847,7 +24086,7 @@ function createMicrosoftProvider(config) {
22847
24086
  }
22848
24087
  function createAppleProvider(config) {
22849
24088
  async function generateClientSecret() {
22850
- return jwt.sign({}, config.privateKey, {
24089
+ return jwt$1.sign({}, config.privateKey, {
22851
24090
  algorithm: "ES256",
22852
24091
  keyid: config.keyId,
22853
24092
  issuer: config.teamId,
@@ -23327,6 +24566,341 @@ function createSpotifyProvider(config) {
23327
24566
  }
23328
24567
  };
23329
24568
  }
24569
+ const TABLE$1 = "rebase.api_keys";
24570
+ function generateApiKey() {
24571
+ const random = randomBytes(16).toString("hex");
24572
+ return `rk_live_${random}`;
24573
+ }
24574
+ function hashKey(plaintext) {
24575
+ return createHash("sha256").update(plaintext).digest("hex");
24576
+ }
24577
+ function keyPrefix(plaintext) {
24578
+ return plaintext.substring(0, 12);
24579
+ }
24580
+ function toMasked(row) {
24581
+ return {
24582
+ id: row.id,
24583
+ name: row.name,
24584
+ key_prefix: row.key_prefix,
24585
+ permissions: row.permissions,
24586
+ rate_limit: row.rate_limit,
24587
+ created_by: row.created_by,
24588
+ created_at: row.created_at,
24589
+ updated_at: row.updated_at,
24590
+ last_used_at: row.last_used_at,
24591
+ expires_at: row.expires_at,
24592
+ revoked_at: row.revoked_at
24593
+ };
24594
+ }
24595
+ function rowToApiKey(row) {
24596
+ const permissions = typeof row.permissions === "string" ? JSON.parse(row.permissions) : row.permissions ?? [];
24597
+ return {
24598
+ id: row.id,
24599
+ name: row.name,
24600
+ key_prefix: row.key_prefix,
24601
+ key_hash: row.key_hash,
24602
+ permissions,
24603
+ rate_limit: row.rate_limit !== null && row.rate_limit !== void 0 ? Number(row.rate_limit) : null,
24604
+ created_by: row.created_by,
24605
+ created_at: new Date(row.created_at).toISOString(),
24606
+ updated_at: new Date(row.updated_at).toISOString(),
24607
+ last_used_at: row.last_used_at ? new Date(row.last_used_at).toISOString() : null,
24608
+ expires_at: row.expires_at ? new Date(row.expires_at).toISOString() : null,
24609
+ revoked_at: row.revoked_at ? new Date(row.revoked_at).toISOString() : null
24610
+ };
24611
+ }
24612
+ function esc(value) {
24613
+ return value.replace(/'/g, "''");
24614
+ }
24615
+ function createApiKeyStore(driver) {
24616
+ const admin = driver.admin;
24617
+ if (!isSQLAdmin(admin)) {
24618
+ logger.warn("⚠️ [api-key-store] DataDriver does not support SQL admin — API keys will not be available.");
24619
+ return void 0;
24620
+ }
24621
+ const exec = admin.executeSql.bind(admin);
24622
+ return {
24623
+ // ── Schema bootstrap ────────────────────────────────────────
24624
+ async ensureTable() {
24625
+ try {
24626
+ await exec("CREATE SCHEMA IF NOT EXISTS rebase");
24627
+ await exec(`
24628
+ CREATE TABLE IF NOT EXISTS ${TABLE$1} (
24629
+ id TEXT PRIMARY KEY DEFAULT gen_random_uuid()::text,
24630
+ name TEXT NOT NULL,
24631
+ key_prefix TEXT NOT NULL,
24632
+ key_hash TEXT NOT NULL UNIQUE,
24633
+ permissions JSONB NOT NULL DEFAULT '[]'::jsonb,
24634
+ rate_limit INTEGER,
24635
+ created_by TEXT NOT NULL,
24636
+ created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
24637
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
24638
+ last_used_at TIMESTAMPTZ,
24639
+ expires_at TIMESTAMPTZ,
24640
+ revoked_at TIMESTAMPTZ
24641
+ )
24642
+ `);
24643
+ await exec(`
24644
+ CREATE INDEX IF NOT EXISTS idx_api_keys_hash
24645
+ ON ${TABLE$1}(key_hash)
24646
+ `);
24647
+ await exec(`
24648
+ CREATE INDEX IF NOT EXISTS idx_api_keys_prefix
24649
+ ON ${TABLE$1}(key_prefix)
24650
+ `);
24651
+ logger.info("✅ API keys table ready");
24652
+ } catch (err) {
24653
+ logger.error("❌ Failed to create API keys table", {
24654
+ error: err
24655
+ });
24656
+ logger.warn("⚠️ Continuing without API keys support.");
24657
+ }
24658
+ },
24659
+ // ── Create ──────────────────────────────────────────────────
24660
+ async createApiKey(request, createdBy) {
24661
+ const plaintext = generateApiKey();
24662
+ const hash = hashKey(plaintext);
24663
+ const prefix = keyPrefix(plaintext);
24664
+ const permissionsJson = JSON.stringify(request.permissions);
24665
+ const rateLimitSql = request.rate_limit !== null && request.rate_limit !== void 0 ? String(request.rate_limit) : "NULL";
24666
+ const expiresAtSql = request.expires_at ? `'${esc(request.expires_at)}'` : "NULL";
24667
+ const rows = await exec(`
24668
+ INSERT INTO ${TABLE$1} (name, key_prefix, key_hash, permissions, rate_limit, created_by, expires_at)
24669
+ VALUES (
24670
+ '${esc(request.name)}',
24671
+ '${esc(prefix)}',
24672
+ '${esc(hash)}',
24673
+ '${esc(permissionsJson)}'::jsonb,
24674
+ ${rateLimitSql},
24675
+ '${esc(createdBy)}',
24676
+ ${expiresAtSql}
24677
+ )
24678
+ RETURNING *
24679
+ `);
24680
+ const apiKey = rowToApiKey(rows[0]);
24681
+ return {
24682
+ ...toMasked(apiKey),
24683
+ key: plaintext
24684
+ };
24685
+ },
24686
+ // ── Lookup by hash ──────────────────────────────────────────
24687
+ async findByKeyHash(hash) {
24688
+ const rows = await exec(`
24689
+ SELECT * FROM ${TABLE$1}
24690
+ WHERE key_hash = '${esc(hash)}'
24691
+ LIMIT 1
24692
+ `);
24693
+ if (rows.length === 0) return null;
24694
+ return rowToApiKey(rows[0]);
24695
+ },
24696
+ // ── List all (masked) ───────────────────────────────────────
24697
+ async listApiKeys() {
24698
+ const rows = await exec(`
24699
+ SELECT * FROM ${TABLE$1}
24700
+ ORDER BY created_at DESC
24701
+ `);
24702
+ return rows.map((r) => toMasked(rowToApiKey(r)));
24703
+ },
24704
+ // ── Get by ID (masked) ──────────────────────────────────────
24705
+ async getApiKeyById(id) {
24706
+ const rows = await exec(`
24707
+ SELECT * FROM ${TABLE$1}
24708
+ WHERE id = '${esc(id)}'
24709
+ LIMIT 1
24710
+ `);
24711
+ if (rows.length === 0) return null;
24712
+ return toMasked(rowToApiKey(rows[0]));
24713
+ },
24714
+ // ── Update ──────────────────────────────────────────────────
24715
+ async updateApiKey(id, updates) {
24716
+ const setClauses = [];
24717
+ if (updates.name !== void 0) {
24718
+ setClauses.push(`name = '${esc(updates.name)}'`);
24719
+ }
24720
+ if (updates.permissions !== void 0) {
24721
+ setClauses.push(`permissions = '${esc(JSON.stringify(updates.permissions))}'::jsonb`);
24722
+ }
24723
+ if (updates.rate_limit !== void 0) {
24724
+ setClauses.push(updates.rate_limit !== null ? `rate_limit = ${updates.rate_limit}` : "rate_limit = NULL");
24725
+ }
24726
+ if (updates.expires_at !== void 0) {
24727
+ setClauses.push(updates.expires_at !== null ? `expires_at = '${esc(updates.expires_at)}'` : "expires_at = NULL");
24728
+ }
24729
+ if (setClauses.length === 0) {
24730
+ return this.getApiKeyById(id);
24731
+ }
24732
+ setClauses.push("updated_at = NOW()");
24733
+ const rows = await exec(`
24734
+ UPDATE ${TABLE$1}
24735
+ SET ${setClauses.join(", ")}
24736
+ WHERE id = '${esc(id)}'
24737
+ RETURNING *
24738
+ `);
24739
+ if (rows.length === 0) return null;
24740
+ return toMasked(rowToApiKey(rows[0]));
24741
+ },
24742
+ // ── Revoke (soft-delete) ────────────────────────────────────
24743
+ async revokeApiKey(id) {
24744
+ const rows = await exec(`
24745
+ UPDATE ${TABLE$1}
24746
+ SET revoked_at = NOW(), updated_at = NOW()
24747
+ WHERE id = '${esc(id)}' AND revoked_at IS NULL
24748
+ RETURNING id
24749
+ `);
24750
+ return rows.length > 0;
24751
+ },
24752
+ // ── Touch last_used_at ──────────────────────────────────────
24753
+ async updateLastUsed(id) {
24754
+ try {
24755
+ await exec(`
24756
+ UPDATE ${TABLE$1}
24757
+ SET last_used_at = NOW()
24758
+ WHERE id = '${esc(id)}'
24759
+ `);
24760
+ } catch (err) {
24761
+ logger.error("[api-key-store] Failed to update last_used_at", {
24762
+ error: err
24763
+ });
24764
+ }
24765
+ }
24766
+ };
24767
+ }
24768
+ function validatePermissions(permissions) {
24769
+ if (!Array.isArray(permissions)) return false;
24770
+ for (const perm of permissions) {
24771
+ if (typeof perm !== "object" || perm === null) return false;
24772
+ if (typeof perm.collection !== "string" || perm.collection.length === 0) return false;
24773
+ if (!Array.isArray(perm.operations) || perm.operations.length === 0) return false;
24774
+ const validOps = /* @__PURE__ */ new Set(["read", "write", "delete"]);
24775
+ for (const op of perm.operations) {
24776
+ if (!validOps.has(op)) return false;
24777
+ }
24778
+ }
24779
+ return true;
24780
+ }
24781
+ function createApiKeyRoutes(options2) {
24782
+ const {
24783
+ store,
24784
+ serviceKey
24785
+ } = options2;
24786
+ const router = new Hono();
24787
+ router.onError(errorHandler);
24788
+ router.use("/*", createRequireAuth({
24789
+ serviceKey
24790
+ }));
24791
+ router.use("/*", requireAdmin);
24792
+ router.get("/", async (c) => {
24793
+ const keys2 = await store.listApiKeys();
24794
+ return c.json({
24795
+ keys: keys2
24796
+ });
24797
+ });
24798
+ router.post("/", async (c) => {
24799
+ const body = await c.req.json();
24800
+ const {
24801
+ name: name2,
24802
+ permissions,
24803
+ rate_limit,
24804
+ expires_at
24805
+ } = body;
24806
+ if (!name2 || typeof name2 !== "string" || name2.trim().length === 0) {
24807
+ throw ApiError.badRequest("Name is required", "INVALID_INPUT");
24808
+ }
24809
+ if (!validatePermissions(permissions)) {
24810
+ throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
24811
+ }
24812
+ if (rate_limit !== void 0 && rate_limit !== null) {
24813
+ if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
24814
+ throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
24815
+ }
24816
+ }
24817
+ if (expires_at !== void 0 && expires_at !== null) {
24818
+ const parsed = new Date(expires_at);
24819
+ if (isNaN(parsed.getTime())) {
24820
+ throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
24821
+ }
24822
+ if (parsed <= /* @__PURE__ */ new Date()) {
24823
+ throw ApiError.badRequest("expires_at must be in the future", "INVALID_INPUT");
24824
+ }
24825
+ }
24826
+ const user = c.get("user");
24827
+ const createdBy = user && typeof user === "object" && "userId" in user ? user.userId : "unknown";
24828
+ const request = {
24829
+ name: name2.trim(),
24830
+ permissions,
24831
+ rate_limit: rate_limit ?? null,
24832
+ expires_at: expires_at ?? null
24833
+ };
24834
+ const keyWithSecret = await store.createApiKey(request, createdBy);
24835
+ return c.json({
24836
+ key: keyWithSecret
24837
+ }, 201);
24838
+ });
24839
+ router.get("/:id", async (c) => {
24840
+ const id = c.req.param("id");
24841
+ const key = await store.getApiKeyById(id);
24842
+ if (!key) {
24843
+ throw ApiError.notFound("API key not found");
24844
+ }
24845
+ return c.json({
24846
+ key
24847
+ });
24848
+ });
24849
+ router.put("/:id", async (c) => {
24850
+ const id = c.req.param("id");
24851
+ const body = await c.req.json();
24852
+ const {
24853
+ name: name2,
24854
+ permissions,
24855
+ rate_limit,
24856
+ expires_at
24857
+ } = body;
24858
+ if (name2 !== void 0) {
24859
+ if (typeof name2 !== "string" || name2.trim().length === 0) {
24860
+ throw ApiError.badRequest("Name must be a non-empty string", "INVALID_INPUT");
24861
+ }
24862
+ }
24863
+ if (permissions !== void 0) {
24864
+ if (!validatePermissions(permissions)) {
24865
+ throw ApiError.badRequest("Permissions must be an array of { collection: string, operations: ('read' | 'write' | 'delete')[] }", "INVALID_INPUT");
24866
+ }
24867
+ }
24868
+ if (rate_limit !== void 0 && rate_limit !== null) {
24869
+ if (typeof rate_limit !== "number" || rate_limit < 1 || !Number.isInteger(rate_limit)) {
24870
+ throw ApiError.badRequest("rate_limit must be a positive integer or null", "INVALID_INPUT");
24871
+ }
24872
+ }
24873
+ if (expires_at !== void 0 && expires_at !== null) {
24874
+ const parsed = new Date(expires_at);
24875
+ if (isNaN(parsed.getTime())) {
24876
+ throw ApiError.badRequest("expires_at must be a valid ISO-8601 date", "INVALID_INPUT");
24877
+ }
24878
+ }
24879
+ const updates = {};
24880
+ if (name2 !== void 0) updates.name = name2.trim();
24881
+ if (permissions !== void 0) updates.permissions = permissions;
24882
+ if (rate_limit !== void 0) updates.rate_limit = rate_limit;
24883
+ if (expires_at !== void 0) updates.expires_at = expires_at;
24884
+ const key = await store.updateApiKey(id, updates);
24885
+ if (!key) {
24886
+ throw ApiError.notFound("API key not found");
24887
+ }
24888
+ return c.json({
24889
+ key
24890
+ });
24891
+ });
24892
+ router.delete("/:id", async (c) => {
24893
+ const id = c.req.param("id");
24894
+ const revoked = await store.revokeApiKey(id);
24895
+ if (!revoked) {
24896
+ throw ApiError.notFound("API key not found or already revoked");
24897
+ }
24898
+ return c.json({
24899
+ success: true
24900
+ });
24901
+ });
24902
+ return router;
24903
+ }
23330
24904
  function createCustomAuthAdapter(options2) {
23331
24905
  const defaultCapabilities = {
23332
24906
  hasBuiltInAuthRoutes: false,
@@ -23879,6 +25453,409 @@ class S3StorageController {
23879
25453
  };
23880
25454
  }
23881
25455
  }
25456
+ let sharpFactory;
25457
+ async function getSharp() {
25458
+ if (!sharpFactory) {
25459
+ try {
25460
+ const mod = await import("sharp");
25461
+ sharpFactory = mod.default;
25462
+ } catch (err) {
25463
+ throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
25464
+ }
25465
+ }
25466
+ if (!sharpFactory) {
25467
+ throw new Error("Failed to load optional 'sharp' dependency for image transformation.");
25468
+ }
25469
+ return sharpFactory;
25470
+ }
25471
+ const MAX_DIMENSION = 4096;
25472
+ const MAX_QUALITY = 100;
25473
+ const MIN_QUALITY = 1;
25474
+ const VALID_FORMATS = /* @__PURE__ */ new Set(["webp", "avif", "jpeg", "png"]);
25475
+ const VALID_FITS = /* @__PURE__ */ new Set(["cover", "contain", "fill", "inside", "outside"]);
25476
+ function parseTransformOptions(query) {
25477
+ const opts = {};
25478
+ let hasTransform = false;
25479
+ if (query.width) {
25480
+ const w2 = parseInt(query.width, 10);
25481
+ if (!Number.isNaN(w2) && w2 > 0) {
25482
+ opts.width = Math.min(w2, MAX_DIMENSION);
25483
+ hasTransform = true;
25484
+ }
25485
+ }
25486
+ if (query.height) {
25487
+ const h2 = parseInt(query.height, 10);
25488
+ if (!Number.isNaN(h2) && h2 > 0) {
25489
+ opts.height = Math.min(h2, MAX_DIMENSION);
25490
+ hasTransform = true;
25491
+ }
25492
+ }
25493
+ if (query.quality) {
25494
+ const q = parseInt(query.quality, 10);
25495
+ if (!Number.isNaN(q)) {
25496
+ opts.quality = Math.min(Math.max(q, MIN_QUALITY), MAX_QUALITY);
25497
+ hasTransform = true;
25498
+ }
25499
+ }
25500
+ if (query.format && VALID_FORMATS.has(query.format)) {
25501
+ opts.format = query.format;
25502
+ hasTransform = true;
25503
+ }
25504
+ if (query.fit && VALID_FITS.has(query.fit)) {
25505
+ opts.fit = query.fit;
25506
+ hasTransform = true;
25507
+ }
25508
+ return hasTransform ? opts : null;
25509
+ }
25510
+ const FORMAT_CONTENT_TYPES = {
25511
+ webp: "image/webp",
25512
+ avif: "image/avif",
25513
+ jpeg: "image/jpeg",
25514
+ png: "image/png"
25515
+ };
25516
+ function isTransformableImage(contentType) {
25517
+ return contentType.startsWith("image/") && !contentType.includes("svg") && !contentType.includes("gif");
25518
+ }
25519
+ async function transformImage(buffer, options2) {
25520
+ const sharp = await getSharp();
25521
+ let pipeline = sharp(buffer);
25522
+ if (options2.width || options2.height) {
25523
+ pipeline = pipeline.resize({
25524
+ width: options2.width,
25525
+ height: options2.height,
25526
+ fit: options2.fit || "cover",
25527
+ withoutEnlargement: true
25528
+ });
25529
+ }
25530
+ const format = options2.format || "webp";
25531
+ const quality = options2.quality || 80;
25532
+ switch (format) {
25533
+ case "webp":
25534
+ pipeline = pipeline.webp({
25535
+ quality
25536
+ });
25537
+ break;
25538
+ case "avif":
25539
+ pipeline = pipeline.avif({
25540
+ quality
25541
+ });
25542
+ break;
25543
+ case "jpeg":
25544
+ pipeline = pipeline.jpeg({
25545
+ quality
25546
+ });
25547
+ break;
25548
+ case "png":
25549
+ pipeline = pipeline.png({
25550
+ quality
25551
+ });
25552
+ break;
25553
+ }
25554
+ const data = await pipeline.toBuffer();
25555
+ return {
25556
+ data,
25557
+ contentType: FORMAT_CONTENT_TYPES[format]
25558
+ };
25559
+ }
25560
+ class TransformCache {
25561
+ cache = /* @__PURE__ */ new Map();
25562
+ maxEntries;
25563
+ maxAgeMs;
25564
+ constructor(maxEntries = 500, maxAgeMs = 36e5) {
25565
+ this.maxEntries = maxEntries;
25566
+ this.maxAgeMs = maxAgeMs;
25567
+ }
25568
+ /** Build a deterministic cache key from file key + transform options. */
25569
+ buildKey(fileKey, options2) {
25570
+ return `${fileKey}::${JSON.stringify(options2)}`;
25571
+ }
25572
+ get(cacheKey) {
25573
+ const entry = this.cache.get(cacheKey);
25574
+ if (!entry) return null;
25575
+ if (Date.now() - entry.timestamp > this.maxAgeMs) {
25576
+ this.cache.delete(cacheKey);
25577
+ return null;
25578
+ }
25579
+ this.cache.delete(cacheKey);
25580
+ this.cache.set(cacheKey, entry);
25581
+ return {
25582
+ data: entry.data,
25583
+ contentType: entry.contentType
25584
+ };
25585
+ }
25586
+ set(cacheKey, data, contentType) {
25587
+ if (this.cache.size >= this.maxEntries) {
25588
+ const oldest = this.cache.keys().next().value;
25589
+ if (oldest !== void 0) this.cache.delete(oldest);
25590
+ }
25591
+ this.cache.set(cacheKey, {
25592
+ data,
25593
+ contentType,
25594
+ timestamp: Date.now()
25595
+ });
25596
+ }
25597
+ }
25598
+ const MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024;
25599
+ const UPLOAD_EXPIRY_MS = 24 * 60 * 60 * 1e3;
25600
+ class TusHandler {
25601
+ constructor(storageBaseDir, storageController) {
25602
+ this.storageController = storageController;
25603
+ this.tusDir = join$1(storageBaseDir, ".tus-uploads");
25604
+ }
25605
+ uploads = /* @__PURE__ */ new Map();
25606
+ tusDir;
25607
+ cleanupTimer;
25608
+ /** Ensure the temp directory exists. */
25609
+ async ensureDir() {
25610
+ if (!existsSync(this.tusDir)) {
25611
+ await mkdir$1(this.tusDir, {
25612
+ recursive: true
25613
+ });
25614
+ }
25615
+ }
25616
+ /** Start periodic cleanup of stale uploads. */
25617
+ startCleanup() {
25618
+ if (this.cleanupTimer) return;
25619
+ this.cleanupTimer = setInterval(() => {
25620
+ void this.cleanupStale();
25621
+ }, 6e4);
25622
+ }
25623
+ /** Remove uploads that have been idle for longer than UPLOAD_EXPIRY_MS. */
25624
+ async cleanupStale() {
25625
+ const now = Date.now();
25626
+ for (const [id, upload] of this.uploads) {
25627
+ if (now - upload.createdAt > UPLOAD_EXPIRY_MS && !upload.completed) {
25628
+ try {
25629
+ await unlink$1(upload.filePath);
25630
+ } catch {
25631
+ }
25632
+ this.uploads.delete(id);
25633
+ }
25634
+ }
25635
+ }
25636
+ // -----------------------------------------------------------------------
25637
+ // TUS Metadata Parsing
25638
+ // -----------------------------------------------------------------------
25639
+ /**
25640
+ * Parse the `Upload-Metadata` header.
25641
+ *
25642
+ * Format: `key base64value,key2 base64value2`
25643
+ */
25644
+ parseMetadata(header) {
25645
+ const metadata = {};
25646
+ if (!header) return metadata;
25647
+ for (const pair of header.split(",")) {
25648
+ const trimmed = pair.trim();
25649
+ const spaceIdx = trimmed.indexOf(" ");
25650
+ if (spaceIdx === -1) {
25651
+ metadata[trimmed] = "";
25652
+ } else {
25653
+ const key = trimmed.substring(0, spaceIdx);
25654
+ const value = Buffer.from(trimmed.substring(spaceIdx + 1), "base64").toString("utf-8");
25655
+ metadata[key] = value;
25656
+ }
25657
+ }
25658
+ return metadata;
25659
+ }
25660
+ // -----------------------------------------------------------------------
25661
+ // Protocol Endpoints
25662
+ // -----------------------------------------------------------------------
25663
+ /** `OPTIONS /tus` — TUS capability discovery. */
25664
+ options() {
25665
+ return new Response(null, {
25666
+ status: 204,
25667
+ headers: {
25668
+ "Tus-Resumable": "1.0.0",
25669
+ "Tus-Version": "1.0.0",
25670
+ "Tus-Extension": "creation,termination",
25671
+ "Tus-Max-Size": String(MAX_UPLOAD_SIZE)
25672
+ }
25673
+ });
25674
+ }
25675
+ /** `POST /tus` — Create a new upload. */
25676
+ async create(c) {
25677
+ await this.ensureDir();
25678
+ const uploadLengthHeader = c.req.header("Upload-Length");
25679
+ if (!uploadLengthHeader) {
25680
+ return c.json({
25681
+ error: "Upload-Length header is required"
25682
+ }, 400);
25683
+ }
25684
+ const uploadLength = parseInt(uploadLengthHeader, 10);
25685
+ if (Number.isNaN(uploadLength) || uploadLength <= 0) {
25686
+ return c.json({
25687
+ error: "Invalid Upload-Length"
25688
+ }, 400);
25689
+ }
25690
+ if (uploadLength > MAX_UPLOAD_SIZE) {
25691
+ return c.json({
25692
+ error: `Upload-Length exceeds maximum of ${MAX_UPLOAD_SIZE} bytes`
25693
+ }, 413);
25694
+ }
25695
+ const metadata = this.parseMetadata(c.req.header("Upload-Metadata") || "");
25696
+ const id = randomUUID$1();
25697
+ const filePath = join$1(this.tusDir, id);
25698
+ await writeFile$1(filePath, Buffer.alloc(0));
25699
+ const upload = {
25700
+ id,
25701
+ size: uploadLength,
25702
+ offset: 0,
25703
+ metadata,
25704
+ createdAt: Date.now(),
25705
+ filePath,
25706
+ bucket: metadata.bucket || void 0,
25707
+ key: metadata.key || metadata.filename || void 0,
25708
+ completed: false
25709
+ };
25710
+ this.uploads.set(id, upload);
25711
+ const reqUrl = new URL(c.req.url);
25712
+ const location = `${reqUrl.origin}${reqUrl.pathname}/${id}`;
25713
+ return new Response(null, {
25714
+ status: 201,
25715
+ headers: {
25716
+ Location: location,
25717
+ "Tus-Resumable": "1.0.0",
25718
+ "Upload-Offset": "0"
25719
+ }
25720
+ });
25721
+ }
25722
+ /** `HEAD /tus/:id` — Query upload progress. */
25723
+ head(c, id) {
25724
+ const upload = this.uploads.get(id);
25725
+ if (!upload) {
25726
+ return c.json({
25727
+ error: "Upload not found"
25728
+ }, 404);
25729
+ }
25730
+ return new Response(null, {
25731
+ status: 200,
25732
+ headers: {
25733
+ "Tus-Resumable": "1.0.0",
25734
+ "Upload-Offset": String(upload.offset),
25735
+ "Upload-Length": String(upload.size),
25736
+ "Cache-Control": "no-store"
25737
+ }
25738
+ });
25739
+ }
25740
+ /** `PATCH /tus/:id` — Append data to an upload. */
25741
+ async patch(c, id) {
25742
+ const upload = this.uploads.get(id);
25743
+ if (!upload) {
25744
+ return c.json({
25745
+ error: "Upload not found"
25746
+ }, 404);
25747
+ }
25748
+ if (upload.completed) {
25749
+ return c.json({
25750
+ error: "Upload already completed"
25751
+ }, 400);
25752
+ }
25753
+ const offsetHeader = c.req.header("Upload-Offset");
25754
+ if (!offsetHeader) {
25755
+ return c.json({
25756
+ error: "Upload-Offset header is required"
25757
+ }, 400);
25758
+ }
25759
+ const offset = parseInt(offsetHeader, 10);
25760
+ if (offset !== upload.offset) {
25761
+ return c.json({
25762
+ error: "Offset mismatch"
25763
+ }, 409);
25764
+ }
25765
+ const contentType = c.req.header("Content-Type");
25766
+ if (contentType !== "application/offset+octet-stream") {
25767
+ return c.json({
25768
+ error: "Content-Type must be application/offset+octet-stream"
25769
+ }, 415);
25770
+ }
25771
+ const body = await c.req.arrayBuffer();
25772
+ const chunk = Buffer.from(body);
25773
+ if (upload.offset + chunk.length > upload.size) {
25774
+ return c.json({
25775
+ error: "Chunk exceeds declared Upload-Length"
25776
+ }, 413);
25777
+ }
25778
+ const fh = await open(upload.filePath, "a");
25779
+ try {
25780
+ await fh.write(chunk);
25781
+ } finally {
25782
+ await fh.close();
25783
+ }
25784
+ upload.offset += chunk.length;
25785
+ if (upload.offset >= upload.size) {
25786
+ await this.finalize(upload);
25787
+ }
25788
+ return new Response(null, {
25789
+ status: 204,
25790
+ headers: {
25791
+ "Tus-Resumable": "1.0.0",
25792
+ "Upload-Offset": String(upload.offset)
25793
+ }
25794
+ });
25795
+ }
25796
+ /** `DELETE /tus/:id` — Cancel and remove an upload. */
25797
+ async delete(c, id) {
25798
+ const upload = this.uploads.get(id);
25799
+ if (!upload) {
25800
+ return c.json({
25801
+ error: "Upload not found"
25802
+ }, 404);
25803
+ }
25804
+ try {
25805
+ await unlink$1(upload.filePath);
25806
+ } catch {
25807
+ }
25808
+ this.uploads.delete(id);
25809
+ return new Response(null, {
25810
+ status: 204,
25811
+ headers: {
25812
+ "Tus-Resumable": "1.0.0"
25813
+ }
25814
+ });
25815
+ }
25816
+ // -----------------------------------------------------------------------
25817
+ // Finalization
25818
+ // -----------------------------------------------------------------------
25819
+ /**
25820
+ * Move a completed upload into the storage controller.
25821
+ */
25822
+ async finalize(upload) {
25823
+ upload.completed = true;
25824
+ if (!this.storageController) {
25825
+ logger.warn("[TUS] Upload completed but no StorageController configured. Temp file remains:", {
25826
+ filePath: upload.filePath
25827
+ });
25828
+ return;
25829
+ }
25830
+ try {
25831
+ const {
25832
+ readFile: readFile2
25833
+ } = await import("fs/promises");
25834
+ const data = await readFile2(upload.filePath);
25835
+ const fileName = upload.key || upload.metadata.filename || upload.id;
25836
+ const mimeType = upload.metadata.contentType || upload.metadata.filetype || "application/octet-stream";
25837
+ const file = new File([data], fileName, {
25838
+ type: mimeType
25839
+ });
25840
+ await this.storageController.putObject({
25841
+ file,
25842
+ key: fileName,
25843
+ bucket: upload.bucket
25844
+ });
25845
+ try {
25846
+ await unlink$1(upload.filePath);
25847
+ } catch {
25848
+ }
25849
+ this.uploads.delete(upload.id);
25850
+ logger.info(`[TUS] Upload ${upload.id} finalized → ${fileName}`);
25851
+ } catch (err) {
25852
+ logger.error(`[TUS] Failed to finalize upload ${upload.id}`, {
25853
+ error: err
25854
+ });
25855
+ }
25856
+ }
25857
+ }
25858
+ const transformCache = new TransformCache();
23882
25859
  function extractWildcardPath(c) {
23883
25860
  const routePath = c.req.routePath;
23884
25861
  const prefix = routePath.replace("/*", "");
@@ -23942,6 +25919,7 @@ function createStorageRoutes(config) {
23942
25919
  throw ApiError.notFound("File not found");
23943
25920
  }
23944
25921
  const filePath = decodeURIComponent(rawPath);
25922
+ const transformOpts = parseTransformOptions(c.req.query());
23945
25923
  if (controller.getType() === "local") {
23946
25924
  const localController = controller;
23947
25925
  const {
@@ -23961,8 +25939,19 @@ function createStorageRoutes(config) {
23961
25939
  } catch {
23962
25940
  }
23963
25941
  }
23964
- c.header("Content-Type", contentType);
23965
25942
  const fileContent = fs$4.readFileSync(absolutePath);
25943
+ if (transformOpts && isTransformableImage(contentType)) {
25944
+ const cacheKey = transformCache.buildKey(filePath, transformOpts);
25945
+ let cached = transformCache.get(cacheKey);
25946
+ if (!cached) {
25947
+ cached = await transformImage(Buffer.from(fileContent), transformOpts);
25948
+ transformCache.set(cacheKey, cached.data, cached.contentType);
25949
+ }
25950
+ c.header("Content-Type", cached.contentType);
25951
+ c.header("Cache-Control", "public, max-age=31536000, immutable");
25952
+ return c.body(new Uint8Array(cached.data));
25953
+ }
25954
+ c.header("Content-Type", contentType);
23966
25955
  return c.body(new Uint8Array(fileContent));
23967
25956
  }
23968
25957
  const {
@@ -23973,7 +25962,20 @@ function createStorageRoutes(config) {
23973
25962
  if (!fileObject) {
23974
25963
  throw ApiError.notFound("File not found");
23975
25964
  }
23976
- c.header("Content-Type", fileObject.type || "application/octet-stream");
25965
+ const remoteContentType = fileObject.type || "application/octet-stream";
25966
+ if (transformOpts && isTransformableImage(remoteContentType)) {
25967
+ const cacheKey = transformCache.buildKey(filePath, transformOpts);
25968
+ let cached = transformCache.get(cacheKey);
25969
+ if (!cached) {
25970
+ const buf2 = Buffer.from(await fileObject.arrayBuffer());
25971
+ cached = await transformImage(buf2, transformOpts);
25972
+ transformCache.set(cacheKey, cached.data, cached.contentType);
25973
+ }
25974
+ c.header("Content-Type", cached.contentType);
25975
+ c.header("Cache-Control", "public, max-age=31536000, immutable");
25976
+ return c.body(new Uint8Array(cached.data));
25977
+ }
25978
+ c.header("Content-Type", remoteContentType);
23977
25979
  c.header("Cache-Control", "public, max-age=3600, immutable");
23978
25980
  const buf = await fileObject.arrayBuffer();
23979
25981
  return c.body(new Uint8Array(buf));
@@ -24069,6 +26071,14 @@ function createStorageRoutes(config) {
24069
26071
  message: "Folder created"
24070
26072
  }, 201);
24071
26073
  });
26074
+ const tusBaseDir = controller.getType() === "local" ? controller.getBasePath() : process.env.STORAGE_PATH || "./uploads";
26075
+ const tusHandler = new TusHandler(tusBaseDir, controller);
26076
+ tusHandler.startCleanup();
26077
+ router.options("/tus", (_c2) => tusHandler.options());
26078
+ router.post("/tus", writeAuthMiddleware, async (c) => tusHandler.create(c));
26079
+ router.get("/tus/:id", readAuthMiddleware, (c) => tusHandler.head(c, c.req.param("id")));
26080
+ router.patch("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.patch(c, c.req.param("id")));
26081
+ router.delete("/tus/:id", writeAuthMiddleware, async (c) => tusHandler.delete(c, c.req.param("id")));
24072
26082
  return router;
24073
26083
  }
24074
26084
  const DEFAULT_STORAGE_ID = "(default)";
@@ -24292,6 +26302,13 @@ function createTransport$1(config) {
24292
26302
  } catch (e) {
24293
26303
  }
24294
26304
  }
26305
+ const getErrorField = (obj, field) => {
26306
+ const err = obj?.error;
26307
+ if (err && typeof err === "object" && err !== null && field in err) {
26308
+ return err[field];
26309
+ }
26310
+ return obj?.[field];
26311
+ };
24295
26312
  if (res.status === 401 && onUnauthorizedHandler) {
24296
26313
  const retried = await onUnauthorizedHandler();
24297
26314
  if (retried) {
@@ -24325,7 +26342,7 @@ function createTransport$1(config) {
24325
26342
  const method = init?.method || "GET";
24326
26343
  fallbackMessage = `Endpoint not found (${method} ${path2}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
24327
26344
  }
24328
- throw new RebaseApiError(retryRes.status, retryBody?.error?.message || retryBody?.message || fallbackMessage || `Request failed with status ${retryRes.status}`, retryBody?.error?.code || retryBody?.code, retryBody?.error?.details || retryBody?.details);
26345
+ throw new RebaseApiError(retryRes.status, String(getErrorField(retryBody, "message") || fallbackMessage || `Request failed with status ${retryRes.status}`), getErrorField(retryBody, "code"), getErrorField(retryBody, "details"));
24329
26346
  }
24330
26347
  return retryBody;
24331
26348
  }
@@ -24336,7 +26353,7 @@ function createTransport$1(config) {
24336
26353
  const method = init?.method || "GET";
24337
26354
  fallbackMessage = `Endpoint not found (${method} ${path2}). This usually means the collection is not registered on the backend, or the frontend API URL configuration (e.g. VITE_API_URL) is missing or pointing to the wrong host.`;
24338
26355
  }
24339
- throw new RebaseApiError(res.status, body?.error?.message || body?.message || fallbackMessage || `Request failed with status ${res.status}`, body?.error?.code || body?.code, body?.error?.details || body?.details);
26356
+ throw new RebaseApiError(res.status, String(getErrorField(body, "message") || fallbackMessage || `Request failed with status ${res.status}`), getErrorField(body, "code"), getErrorField(body, "details"));
24340
26357
  }
24341
26358
  return body;
24342
26359
  }
@@ -24998,118 +27015,6 @@ function createCron(transport, options2) {
24998
27015
  toggleJob
24999
27016
  };
25000
27017
  }
25001
- function mapOperator(op) {
25002
- switch (op) {
25003
- case "==":
25004
- return "eq";
25005
- case "!=":
25006
- return "neq";
25007
- case ">":
25008
- return "gt";
25009
- case ">=":
25010
- return "gte";
25011
- case "<":
25012
- return "lt";
25013
- case "<=":
25014
- return "lte";
25015
- case "array-contains":
25016
- return "cs";
25017
- case "array-contains-any":
25018
- return "csa";
25019
- case "not-in":
25020
- return "nin";
25021
- default:
25022
- return op;
25023
- }
25024
- }
25025
- class QueryBuilder {
25026
- constructor(collection) {
25027
- this.collection = collection;
25028
- }
25029
- params = {
25030
- where: {}
25031
- };
25032
- /**
25033
- * Add a filter condition to your query.
25034
- * @example
25035
- * client.collection('users').where('age', '>=', 18).find()
25036
- */
25037
- where(column, operator, value) {
25038
- if (!this.params.where) {
25039
- this.params.where = {};
25040
- }
25041
- const mappedOp = mapOperator(operator);
25042
- let formattedValue = value;
25043
- if (Array.isArray(value) && ["in", "nin", "cs", "csa"].includes(mappedOp)) {
25044
- formattedValue = `(${value.join(",")})`;
25045
- } else if (value === null) {
25046
- formattedValue = "null";
25047
- }
25048
- this.params.where[column] = mappedOp === "eq" ? String(formattedValue) : `${mappedOp}.${formattedValue}`;
25049
- return this;
25050
- }
25051
- /**
25052
- * Order the results by a specific column.
25053
- * @example
25054
- * client.collection('users').orderBy('createdAt', 'desc').find()
25055
- */
25056
- orderBy(column, ascending = "asc") {
25057
- this.params.orderBy = `${column}:${ascending}`;
25058
- return this;
25059
- }
25060
- /**
25061
- * Limit the number of results returned.
25062
- */
25063
- limit(count) {
25064
- this.params.limit = count;
25065
- return this;
25066
- }
25067
- /**
25068
- * Skip the first N results.
25069
- */
25070
- offset(count) {
25071
- this.params.offset = count;
25072
- return this;
25073
- }
25074
- /**
25075
- * Set a free-text search string if supported by the backend.
25076
- */
25077
- search(searchString) {
25078
- this.params.searchString = searchString;
25079
- return this;
25080
- }
25081
- /**
25082
- * Include related entities in the response.
25083
- * Relations will be populated with full entity data instead of just IDs.
25084
- *
25085
- * @param relations - Relation names to include, or "*" for all.
25086
- * @example
25087
- * // Include specific relations
25088
- * client.data.posts.include("tags", "author").find()
25089
- *
25090
- * // Include all relations
25091
- * client.data.posts.include("*").find()
25092
- */
25093
- include(...relations) {
25094
- this.params.include = relations;
25095
- return this;
25096
- }
25097
- /**
25098
- * Execute the find query and return the results.
25099
- */
25100
- async find() {
25101
- return this.collection.find(this.params);
25102
- }
25103
- /**
25104
- * Listen to realtime updates matching this query.
25105
- */
25106
- listen(onUpdate, onError) {
25107
- if (!this.collection.listen) {
25108
- throw new Error("Listen is only available when RebaseClient is configured with a websocketUrl.");
25109
- }
25110
- return this.collection.listen(this.params, onUpdate, onError);
25111
- }
25112
- }
25113
27018
  function rowToEntity(row, slug) {
25114
27019
  return {
25115
27020
  id: row.id,
@@ -25704,7 +27609,7 @@ const require$$9 = {
25704
27609
  const http = require$$0$6;
25705
27610
  const https = require$$1;
25706
27611
  const urllib$2 = require$$0$5;
25707
- const zlib = require$$11;
27612
+ const zlib = require$$3;
25708
27613
  const PassThrough$3 = require$$0$4.PassThrough;
25709
27614
  const Cookies2 = cookies;
25710
27615
  const packageData$7 = require$$9;
@@ -25939,9 +27844,9 @@ var fetchExports = fetch$1.exports;
25939
27844
  const util2 = require$$5;
25940
27845
  const fs2 = fs__default;
25941
27846
  const nmfetch2 = fetchExports;
25942
- const dns2 = require$$11;
27847
+ const dns2 = require$$4$1;
25943
27848
  const net2 = require$$0$7;
25944
- const os2 = require$$1$2;
27849
+ const os2 = require$$1$3;
25945
27850
  const DNS_TTL = 5 * 60 * 1e3;
25946
27851
  let networkInterfaces;
25947
27852
  try {
@@ -32087,7 +33992,7 @@ const urllib = require$$0$5;
32087
33992
  const packageData$6 = require$$9;
32088
33993
  const MailMessage2 = mailMessage;
32089
33994
  const net$1 = require$$0$7;
32090
- const dns = require$$11;
33995
+ const dns = require$$4$1;
32091
33996
  const crypto$3 = require$$0__default;
32092
33997
  class Mail extends EventEmitter$5 {
32093
33998
  constructor(transporter, options2, defaults) {
@@ -32523,7 +34428,7 @@ const packageInfo = require$$9;
32523
34428
  const EventEmitter$4 = require$$0$9.EventEmitter;
32524
34429
  const net = require$$0$7;
32525
34430
  const tls = require$$1$1;
32526
- const os = require$$1$2;
34431
+ const os = require$$1$3;
32527
34432
  const crypto$2 = require$$0__default;
32528
34433
  const DataStream2 = dataStream;
32529
34434
  const PassThrough = require$$0$4.PassThrough;
@@ -36711,7 +38616,7 @@ async function _initializeRebaseBackend(config) {
36711
38616
  oauthProviders,
36712
38617
  serviceKey,
36713
38618
  hooks: config.hooks,
36714
- overrides: safeAuthConfig.overrides
38619
+ authHooks: safeAuthConfig.hooks
36715
38620
  });
36716
38621
  }
36717
38622
  logger.info("Authentication initialized");
@@ -36778,73 +38683,73 @@ async function _initializeRebaseBackend(config) {
36778
38683
  if (safeAuthConfig.google?.clientId) {
36779
38684
  const {
36780
38685
  createGoogleProvider: createGoogleProvider2
36781
- } = await import("./index-BZoAtuqi.js");
38686
+ } = await import("./index-Cr1D21av.js");
36782
38687
  oauthProviders.push(createGoogleProvider2(safeAuthConfig.google));
36783
38688
  }
36784
38689
  if (safeAuthConfig.linkedin?.clientId && safeAuthConfig.linkedin?.clientSecret) {
36785
38690
  const {
36786
38691
  createLinkedinProvider: createLinkedinProvider2
36787
- } = await import("./index-BZoAtuqi.js");
38692
+ } = await import("./index-Cr1D21av.js");
36788
38693
  oauthProviders.push(createLinkedinProvider2(safeAuthConfig.linkedin));
36789
38694
  }
36790
38695
  if (safeAuthConfig.github?.clientId && safeAuthConfig.github?.clientSecret) {
36791
38696
  const {
36792
38697
  createGitHubProvider: createGitHubProvider2
36793
- } = await import("./index-BZoAtuqi.js");
38698
+ } = await import("./index-Cr1D21av.js");
36794
38699
  oauthProviders.push(createGitHubProvider2(safeAuthConfig.github));
36795
38700
  }
36796
38701
  if (safeAuthConfig.microsoft?.clientId && safeAuthConfig.microsoft?.clientSecret) {
36797
38702
  const {
36798
38703
  createMicrosoftProvider: createMicrosoftProvider2
36799
- } = await import("./index-BZoAtuqi.js");
38704
+ } = await import("./index-Cr1D21av.js");
36800
38705
  oauthProviders.push(createMicrosoftProvider2(safeAuthConfig.microsoft));
36801
38706
  }
36802
38707
  if (safeAuthConfig.apple?.clientId && safeAuthConfig.apple?.teamId && safeAuthConfig.apple?.keyId && safeAuthConfig.apple?.privateKey) {
36803
38708
  const {
36804
38709
  createAppleProvider: createAppleProvider2
36805
- } = await import("./index-BZoAtuqi.js");
38710
+ } = await import("./index-Cr1D21av.js");
36806
38711
  oauthProviders.push(createAppleProvider2(safeAuthConfig.apple));
36807
38712
  }
36808
38713
  if (safeAuthConfig.facebook?.clientId && safeAuthConfig.facebook?.clientSecret) {
36809
38714
  const {
36810
38715
  createFacebookProvider: createFacebookProvider2
36811
- } = await import("./index-BZoAtuqi.js");
38716
+ } = await import("./index-Cr1D21av.js");
36812
38717
  oauthProviders.push(createFacebookProvider2(safeAuthConfig.facebook));
36813
38718
  }
36814
38719
  if (safeAuthConfig.twitter?.clientId && safeAuthConfig.twitter?.clientSecret) {
36815
38720
  const {
36816
38721
  createTwitterProvider: createTwitterProvider2
36817
- } = await import("./index-BZoAtuqi.js");
38722
+ } = await import("./index-Cr1D21av.js");
36818
38723
  oauthProviders.push(createTwitterProvider2(safeAuthConfig.twitter));
36819
38724
  }
36820
38725
  if (safeAuthConfig.discord?.clientId && safeAuthConfig.discord?.clientSecret) {
36821
38726
  const {
36822
38727
  createDiscordProvider: createDiscordProvider2
36823
- } = await import("./index-BZoAtuqi.js");
38728
+ } = await import("./index-Cr1D21av.js");
36824
38729
  oauthProviders.push(createDiscordProvider2(safeAuthConfig.discord));
36825
38730
  }
36826
38731
  if (safeAuthConfig.gitlab?.clientId && safeAuthConfig.gitlab?.clientSecret) {
36827
38732
  const {
36828
38733
  createGitLabProvider: createGitLabProvider2
36829
- } = await import("./index-BZoAtuqi.js");
38734
+ } = await import("./index-Cr1D21av.js");
36830
38735
  oauthProviders.push(createGitLabProvider2(safeAuthConfig.gitlab));
36831
38736
  }
36832
38737
  if (safeAuthConfig.bitbucket?.clientId && safeAuthConfig.bitbucket?.clientSecret) {
36833
38738
  const {
36834
38739
  createBitbucketProvider: createBitbucketProvider2
36835
- } = await import("./index-BZoAtuqi.js");
38740
+ } = await import("./index-Cr1D21av.js");
36836
38741
  oauthProviders.push(createBitbucketProvider2(safeAuthConfig.bitbucket));
36837
38742
  }
36838
38743
  if (safeAuthConfig.slack?.clientId && safeAuthConfig.slack?.clientSecret) {
36839
38744
  const {
36840
38745
  createSlackProvider: createSlackProvider2
36841
- } = await import("./index-BZoAtuqi.js");
38746
+ } = await import("./index-Cr1D21av.js");
36842
38747
  oauthProviders.push(createSlackProvider2(safeAuthConfig.slack));
36843
38748
  }
36844
38749
  if (safeAuthConfig.spotify?.clientId && safeAuthConfig.spotify?.clientSecret) {
36845
38750
  const {
36846
38751
  createSpotifyProvider: createSpotifyProvider2
36847
- } = await import("./index-BZoAtuqi.js");
38752
+ } = await import("./index-Cr1D21av.js");
36848
38753
  oauthProviders.push(createSpotifyProvider2(safeAuthConfig.spotify));
36849
38754
  }
36850
38755
  authAdapter = createBuiltinAuthAdapter({
@@ -36856,7 +38761,7 @@ async function _initializeRebaseBackend(config) {
36856
38761
  oauthProviders,
36857
38762
  serviceKey,
36858
38763
  hooks: config.hooks,
36859
- overrides: safeAuthConfig.overrides
38764
+ authHooks: safeAuthConfig.hooks
36860
38765
  });
36861
38766
  }
36862
38767
  if (authAdapter.createAuthRoutes) {
@@ -36878,6 +38783,21 @@ async function _initializeRebaseBackend(config) {
36878
38783
  }
36879
38784
  }
36880
38785
  }
38786
+ let apiKeyStore;
38787
+ const apiKeyStoreResult = createApiKeyStore(defaultDriver);
38788
+ if (apiKeyStoreResult) {
38789
+ apiKeyStore = apiKeyStoreResult;
38790
+ await apiKeyStore.ensureTable();
38791
+ logger.info("Service API Keys initialized");
38792
+ const apiKeyRoutes = createApiKeyRoutes({
38793
+ store: apiKeyStore,
38794
+ serviceKey
38795
+ });
38796
+ config.app.route(`${basePath}/admin/api-keys`, apiKeyRoutes);
38797
+ logger.info("API key admin routes mounted", {
38798
+ path: `${basePath}/admin/api-keys`
38799
+ });
38800
+ }
36881
38801
  if (config.collectionsDir) {
36882
38802
  if (process.env.NODE_ENV !== "production") {
36883
38803
  const {
@@ -36928,15 +38848,20 @@ async function _initializeRebaseBackend(config) {
36928
38848
  dataRouter.use("/*", createAdapterAuthMiddleware({
36929
38849
  adapter: authAdapter,
36930
38850
  driver: defaultDriver,
36931
- requireAuth: dataRequireAuth
38851
+ requireAuth: dataRequireAuth,
38852
+ apiKeyStore
36932
38853
  }));
36933
38854
  } else {
36934
38855
  dataRouter.use("/*", createAuthMiddleware({
36935
38856
  driver: defaultDriver,
36936
38857
  requireAuth: dataRequireAuth,
36937
- serviceKey
38858
+ serviceKey,
38859
+ apiKeyStore
36938
38860
  }));
36939
38861
  }
38862
+ if (apiKeyStore) {
38863
+ dataRouter.use("/*", createApiKeyRateLimiter());
38864
+ }
36940
38865
  if (historyConfigResult && historyConfigResult.historyService) {
36941
38866
  const historyRoutes = createHistoryRoutes({
36942
38867
  historyService: historyConfigResult.historyService,
@@ -37030,13 +38955,15 @@ async function _initializeRebaseBackend(config) {
37030
38955
  functionsRouter.use("/*", createAdapterAuthMiddleware({
37031
38956
  adapter: authAdapter,
37032
38957
  driver: defaultDriver,
37033
- requireAuth: functionsRequireAuth
38958
+ requireAuth: functionsRequireAuth,
38959
+ apiKeyStore
37034
38960
  }));
37035
38961
  } else {
37036
38962
  functionsRouter.use("/*", createAuthMiddleware({
37037
38963
  driver: defaultDriver,
37038
38964
  requireAuth: functionsRequireAuth,
37039
- serviceKey
38965
+ serviceKey,
38966
+ apiKeyStore
37040
38967
  }));
37041
38968
  }
37042
38969
  const fnRoutes = createFunctionRoutes2(loadedFunctions);
@@ -47027,7 +48954,7 @@ class AstSchemaEditor {
47027
48954
  return "undefined";
47028
48955
  }
47029
48956
  if (typeof obj === "string") {
47030
- return `"${obj.replace(/"/g, '\\"')}"`;
48957
+ return JSON.stringify(obj);
47031
48958
  }
47032
48959
  if (typeof obj === "number" || typeof obj === "boolean") {
47033
48960
  return String(obj);
@@ -47058,7 +48985,7 @@ ${indent2}]`;
47058
48985
  const kind = init.getKind();
47059
48986
  const isCode = kind === SyntaxKind.ArrowFunction || kind === SyntaxKind.FunctionExpression || kind === SyntaxKind.Identifier || kind === SyntaxKind.CallExpression || kind === SyntaxKind.JsxElement;
47060
48987
  if (isCode || name2 === "target" || name2 === "callbacks" || name2 === "permissions" || name2 === "securityRules") {
47061
- const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 : `"${name2}"`;
48988
+ const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name2) ? name2 : JSON.stringify(name2);
47062
48989
  preservedProps.push(`${keyStr}: ${init.getText()}`);
47063
48990
  }
47064
48991
  }
@@ -47068,7 +48995,7 @@ ${indent2}]`;
47068
48995
  }
47069
48996
  if (keys2.length === 0 && preservedProps.length === 0) return "{}";
47070
48997
  const props = keys2.map((key) => {
47071
- const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : `"${key}"`;
48998
+ const keyStr = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key) ? key : JSON.stringify(key);
47072
48999
  let childAstNode;
47073
49000
  if (oldAstNode && typeof record[key] === "object" && record[key] !== null && !Array.isArray(record[key])) {
47074
49001
  const oldProp = oldAstNode.getProperty((p) => "getName" in p && typeof p.getName === "function" && (p.getName() === key || p.getName() === `"${key}"` || p.getName() === `'${key}'`));
@@ -47110,7 +49037,7 @@ ${indent2}}`;
47110
49037
  }
47111
49038
  } else {
47112
49039
  propsObj.addPropertyAssignment({
47113
- name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : `"${propertyKey}"`,
49040
+ name: /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(propertyKey) ? propertyKey : JSON.stringify(propertyKey),
47114
49041
  initializer: newInitializer
47115
49042
  });
47116
49043
  }
@@ -48181,7 +50108,7 @@ async function loadFunctionsFromDirectory(directory) {
48181
50108
  const mod = await dynamicImport(fileUrl);
48182
50109
  const exported = mod.default;
48183
50110
  if (!exported) {
48184
- console.warn(`[functions] ${file}: no default export. Skipping.`);
50111
+ logger.warn(`[functions] ${file}: no default export. Skipping.`);
48185
50112
  continue;
48186
50113
  }
48187
50114
  if (isHonoLike(exported)) {
@@ -48190,7 +50117,7 @@ async function loadFunctionsFromDirectory(directory) {
48190
50117
  name: name2,
48191
50118
  app: exported
48192
50119
  });
48193
- console.log(`⚡ Loaded function route: ${name2}`);
50120
+ logger.info(`⚡ Loaded function route: ${name2}`);
48194
50121
  continue;
48195
50122
  }
48196
50123
  if (typeof exported === "function") {
@@ -48201,20 +50128,20 @@ async function loadFunctionsFromDirectory(directory) {
48201
50128
  name: name2,
48202
50129
  app: result
48203
50130
  });
48204
- console.log(`⚡ Loaded function route: ${name2}`);
50131
+ logger.info(`⚡ Loaded function route: ${name2}`);
48205
50132
  continue;
48206
50133
  }
48207
50134
  }
48208
50135
  const exportType = typeof exported;
48209
50136
  const keys2 = exported && typeof exported === "object" ? Object.getOwnPropertyNames(Object.getPrototypeOf(exported)).slice(0, 10).join(", ") : "N/A";
48210
- console.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.
50137
+ logger.warn(`[functions] ${file}: default export is not a Hono app or factory. Skipping.
48211
50138
  export type: ${exportType}${exported?.constructor?.name ? ` (${exported.constructor.name})` : ""}
48212
50139
  prototype methods: ${keys2}
48213
50140
  Hint: ensure the function exports a Hono app created with the same hono version as the server.
48214
50141
  The loader checks for .fetch() and .routes — any Hono-compatible app will work.`);
48215
50142
  } catch (err) {
48216
50143
  const message = err instanceof Error ? err.message : String(err);
48217
- console.error(`[functions] Failed to load ${file}: ${message}`);
50144
+ logger.error(`[functions] Failed to load ${file}: ${message}`);
48218
50145
  }
48219
50146
  }
48220
50147
  }
@@ -48263,12 +50190,12 @@ async function loadCronJobsFromDirectory(directory) {
48263
50190
  const mod = await dynamicImport(fileUrl);
48264
50191
  const exported = mod.default;
48265
50192
  if (!exported || typeof exported !== "object") {
48266
- console.warn(`[cron] ${file}: no valid default export. Skipping.`);
50193
+ logger.warn(`[cron] ${file}: no valid default export. Skipping.`);
48267
50194
  continue;
48268
50195
  }
48269
50196
  const def = exported;
48270
50197
  if (typeof def.schedule !== "string" || typeof def.handler !== "function") {
48271
- console.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
50198
+ logger.warn(`[cron] ${file}: default export missing required 'schedule' or 'handler'. Skipping.`);
48272
50199
  continue;
48273
50200
  }
48274
50201
  const id = path$3.basename(file, path$3.extname(file));
@@ -48284,10 +50211,10 @@ async function loadCronJobsFromDirectory(directory) {
48284
50211
  id,
48285
50212
  definition
48286
50213
  });
48287
- console.log(`⏰ Loaded cron job: ${id} (${definition.schedule})`);
50214
+ logger.info(`⏰ Loaded cron job: ${id} (${definition.schedule})`);
48288
50215
  } catch (err) {
48289
50216
  const message = err instanceof Error ? err.message : String(err);
48290
- console.error(`[cron] Failed to load ${file}: ${message}`);
50217
+ logger.error(`[cron] Failed to load ${file}: ${message}`);
48291
50218
  }
48292
50219
  }
48293
50220
  }
@@ -48442,12 +50369,12 @@ class CronScheduler {
48442
50369
  for (const loaded of loadedJobs) {
48443
50370
  const validation = validateCronExpression(loaded.definition.schedule);
48444
50371
  if (!validation.valid) {
48445
- console.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
50372
+ logger.error(`[cron] Rejecting job "${loaded.id}": invalid schedule "${loaded.definition.schedule}" — ${validation.reason}`);
48446
50373
  continue;
48447
50374
  }
48448
50375
  const existing = this.jobs.get(loaded.id);
48449
50376
  if (existing) {
48450
- console.warn(`[cron] Duplicate cron job id: "${loaded.id}". Overwriting.`);
50377
+ logger.warn(`[cron] Duplicate cron job id: "${loaded.id}". Overwriting.`);
48451
50378
  this.stopJob(loaded.id);
48452
50379
  }
48453
50380
  const enabled = loaded.definition.enabled !== false;
@@ -48485,7 +50412,9 @@ class CronScheduler {
48485
50412
  }
48486
50413
  }
48487
50414
  }).catch((err) => {
48488
- console.warn("[cron] Failed to seed job stats from database:", err);
50415
+ logger.warn("[cron] Failed to seed job stats from database", {
50416
+ error: err
50417
+ });
48489
50418
  });
48490
50419
  }
48491
50420
  for (const [id, job] of this.jobs) {
@@ -48493,7 +50422,7 @@ class CronScheduler {
48493
50422
  this.scheduleNext(id);
48494
50423
  }
48495
50424
  }
48496
- console.log(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
50425
+ logger.info(`⏰ Cron scheduler started with ${this.jobs.size} job(s)`);
48497
50426
  }
48498
50427
  /**
48499
50428
  * Stop the scheduler and clear all timers.
@@ -48567,7 +50496,7 @@ class CronScheduler {
48567
50496
  const job = this.jobs.get(id);
48568
50497
  if (!job) return void 0;
48569
50498
  if (job.executing) {
48570
- console.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
50499
+ logger.warn(`[cron] Skipping manual trigger of "${id}" — already executing`);
48571
50500
  const logEntry = {
48572
50501
  jobId: id,
48573
50502
  startedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -48611,7 +50540,7 @@ class CronScheduler {
48611
50540
  const timer = setTimeout(async () => {
48612
50541
  if (!job.enabled || !this.started) return;
48613
50542
  if (job.executing) {
48614
- console.warn(`[cron] Skipping scheduled run of "${id}" — still executing from previous run`);
50543
+ logger.warn(`[cron] Skipping scheduled run of "${id}" — still executing from previous run`);
48615
50544
  this.scheduleNext(id);
48616
50545
  return;
48617
50546
  }
@@ -48625,7 +50554,9 @@ class CronScheduler {
48625
50554
  }
48626
50555
  job.timerId = timer;
48627
50556
  } catch (err) {
48628
- console.error(`[cron] Failed to schedule "${id}":`, err);
50557
+ logger.error(`[cron] Failed to schedule "${id}"`, {
50558
+ error: err
50559
+ });
48629
50560
  job.state = "error";
48630
50561
  job.lastError = err instanceof Error ? err.message : String(err);
48631
50562
  }
@@ -48710,13 +50641,15 @@ class CronScheduler {
48710
50641
  }
48711
50642
  if (this.store) {
48712
50643
  this.store.insertLog(logEntry).catch((persistErr) => {
48713
- console.error(`[cron] Failed to persist log for "${job.id}":`, persistErr);
50644
+ logger.error(`[cron] Failed to persist log for "${job.id}"`, {
50645
+ error: persistErr
50646
+ });
48714
50647
  });
48715
50648
  }
48716
50649
  if (success) {
48717
- console.log(`✅ [cron] "${job.id}" completed in ${durationMs}ms`);
50650
+ logger.info(`✅ [cron] "${job.id}" completed in ${durationMs}ms`);
48718
50651
  } else {
48719
- console.error(`❌ [cron] "${job.id}" failed in ${durationMs}ms: ${error2}`);
50652
+ logger.error(`❌ [cron] "${job.id}" failed in ${durationMs}ms: ${error2}`);
48720
50653
  }
48721
50654
  return logEntry;
48722
50655
  }
@@ -48834,7 +50767,7 @@ const TABLE = "rebase.cron_logs";
48834
50767
  function createCronStore(driver) {
48835
50768
  const admin = driver.admin;
48836
50769
  if (!isSQLAdmin(admin)) {
48837
- console.warn("⚠️ [cron-store] DataDriver does not support SQL admin — cron logs will not be persisted.");
50770
+ logger.warn("⚠️ [cron-store] DataDriver does not support SQL admin — cron logs will not be persisted.");
48838
50771
  return void 0;
48839
50772
  }
48840
50773
  const exec = admin.executeSql.bind(admin);
@@ -48860,10 +50793,12 @@ function createCronStore(driver) {
48860
50793
  CREATE INDEX IF NOT EXISTS idx_cron_logs_job
48861
50794
  ON ${TABLE}(job_id, started_at DESC)
48862
50795
  `);
48863
- console.log("✅ Cron logs table ready");
50796
+ logger.info("✅ Cron logs table ready");
48864
50797
  } catch (err) {
48865
- console.error("❌ Failed to create cron logs table:", err);
48866
- console.warn("⚠️ Continuing without cron log persistence.");
50798
+ logger.error("❌ Failed to create cron logs table", {
50799
+ error: err
50800
+ });
50801
+ logger.warn("⚠️ Continuing without cron log persistence.");
48867
50802
  }
48868
50803
  },
48869
50804
  async insertLog(entry) {
@@ -48886,7 +50821,9 @@ function createCronStore(driver) {
48886
50821
  )
48887
50822
  `);
48888
50823
  } catch (err) {
48889
- console.error(`[cron-store] Failed to persist log for "${entry.jobId}":`, err);
50824
+ logger.error(`[cron-store] Failed to persist log for "${entry.jobId}"`, {
50825
+ error: err
50826
+ });
48890
50827
  }
48891
50828
  },
48892
50829
  async fetchLogs(jobId, limit = 50) {
@@ -48900,7 +50837,9 @@ function createCronStore(driver) {
48900
50837
  `);
48901
50838
  return rows.map(rowToLogEntry);
48902
50839
  } catch (err) {
48903
- console.error(`[cron-store] Failed to fetch logs for "${jobId}":`, err);
50840
+ logger.error(`[cron-store] Failed to fetch logs for "${jobId}"`, {
50841
+ error: err
50842
+ });
48904
50843
  return [];
48905
50844
  }
48906
50845
  },
@@ -48924,7 +50863,9 @@ function createCronStore(driver) {
48924
50863
  });
48925
50864
  }
48926
50865
  } catch (err) {
48927
- console.error("[cron-store] Failed to fetch job stats:", err);
50866
+ logger.error("[cron-store] Failed to fetch job stats", {
50867
+ error: err
50868
+ });
48928
50869
  }
48929
50870
  return stats;
48930
50871
  }
@@ -48955,8 +50896,8 @@ function serveSPA(app, config) {
48955
50896
  indexFile = "index.html"
48956
50897
  } = config;
48957
50898
  if (!fs$4.existsSync(frontendPath)) {
48958
- console.warn(`⚠️ Frontend build path does not exist: ${frontendPath}`);
48959
- console.warn(" SPA serving is disabled. Build your frontend first.");
50899
+ logger.warn(`⚠️ Frontend build path does not exist: ${frontendPath}`);
50900
+ logger.warn(" SPA serving is disabled. Build your frontend first.");
48960
50901
  return;
48961
50902
  }
48962
50903
  app.use("/*", serveStatic({
@@ -48969,13 +50910,13 @@ function serveSPA(app, config) {
48969
50910
  }
48970
50911
  const indexPath = path$3.join(frontendPath, indexFile);
48971
50912
  if (!fs$4.existsSync(indexPath)) {
48972
- console.warn(`⚠️ Index file not found: ${indexPath}`);
50913
+ logger.warn(`⚠️ Index file not found: ${indexPath}`);
48973
50914
  return next();
48974
50915
  }
48975
50916
  const html = fs$4.readFileSync(indexPath, "utf-8");
48976
50917
  return c.html(html);
48977
50918
  });
48978
- console.log(`✅ SPA serving enabled from: ${frontendPath}`);
50919
+ logger.info(`✅ SPA serving enabled from: ${frontendPath}`);
48979
50920
  }
48980
50921
  const MAX_PORT_ATTEMPTS = 20;
48981
50922
  const DEV_PORT_FILENAME = ".rebase-dev-port";
@@ -48983,6 +50924,19 @@ function listenWithPortRetry(server, startPort, options2) {
48983
50924
  const host = options2?.host ?? "0.0.0.0";
48984
50925
  const maxAttempts = options2?.maxAttempts ?? MAX_PORT_ATTEMPTS;
48985
50926
  const portFileDir = options2?.portFileDir;
50927
+ const isProd = process.env.NODE_ENV === "production";
50928
+ if (isProd) {
50929
+ return new Promise((resolve, reject) => {
50930
+ const onError = (err) => {
50931
+ reject(err);
50932
+ };
50933
+ server.once("error", onError);
50934
+ server.listen(startPort, host, () => {
50935
+ server.removeListener("error", onError);
50936
+ resolve(startPort);
50937
+ });
50938
+ });
50939
+ }
48986
50940
  let affinityPort = null;
48987
50941
  if (portFileDir) {
48988
50942
  try {
@@ -49125,6 +51079,8 @@ const rebaseEnvSchema = objectType({
49125
51079
  DB_POOL_MAX: stringType().default("20").transform(Number),
49126
51080
  DB_POOL_IDLE_TIMEOUT: stringType().default("30000").transform(Number),
49127
51081
  DB_POOL_CONNECT_TIMEOUT: stringType().default("10000").transform(Number),
51082
+ DATABASE_DIRECT_URL: stringType().url().optional(),
51083
+ DATABASE_READ_URL: stringType().url().optional(),
49128
51084
  FORCE_LOCAL_STORAGE: optionalBoolString,
49129
51085
  STORAGE_TYPE: enumType(["local", "s3"]).default("local"),
49130
51086
  STORAGE_PATH: stringType().optional(),
@@ -49201,8 +51157,11 @@ export {
49201
51157
  RestApiGenerator,
49202
51158
  S3StorageController,
49203
51159
  SMTPEmailService,
51160
+ TransformCache,
51161
+ TusHandler,
49204
51162
  _resetRebaseMock,
49205
51163
  _setRebaseMock,
51164
+ apiKeyKeyGenerator,
49206
51165
  authJwt,
49207
51166
  authRoles,
49208
51167
  authUid,
@@ -49211,6 +51170,9 @@ export {
49211
51170
  configureLogLevel,
49212
51171
  createAdapterAuthMiddleware,
49213
51172
  createAdminRoutes,
51173
+ createApiKeyRateLimiter,
51174
+ createApiKeyRoutes,
51175
+ createApiKeyStore,
49214
51176
  createAppleProvider,
49215
51177
  createAuthMiddleware,
49216
51178
  createAuthRoutes,
@@ -49248,26 +51210,34 @@ export {
49248
51210
  getWelcomeEmailTemplate,
49249
51211
  hashPassword,
49250
51212
  hashRefreshToken,
51213
+ httpMethodToOperation,
49251
51214
  initializeRebaseBackend,
51215
+ isApiKeyToken,
49252
51216
  isAuthAdapter,
49253
51217
  isDatabaseAdapter,
51218
+ isOperationAllowed,
51219
+ isTransformableImage,
49254
51220
  listenWithPortRetry,
49255
51221
  loadCronJobsFromDirectory,
49256
51222
  loadEnv,
49257
51223
  loadFunctionsFromDirectory,
49258
51224
  logger,
49259
51225
  optionalAuth,
51226
+ parseTransformOptions,
49260
51227
  rebase,
49261
51228
  requestLogger,
49262
51229
  requireAdmin,
49263
51230
  requireAuth,
49264
51231
  resetConsole,
49265
- resolveAuthOverrides,
51232
+ resolveAuthHooks,
49266
51233
  serveSPA,
49267
51234
  strictAuthLimiter,
51235
+ transformImage,
51236
+ validateApiKey,
49268
51237
  validateCronExpression,
49269
51238
  validatePasswordStrength,
49270
51239
  verifyAccessToken,
49271
- verifyPassword
51240
+ verifyPassword,
51241
+ z
49272
51242
  };
49273
51243
  //# sourceMappingURL=index.es.js.map