@xtr-dev/rondevu-server 0.2.4 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -757,12 +757,35 @@ function createApp(storage, config) {
757
757
  return c.json({ error: "Internal server error" }, 500);
758
758
  }
759
759
  });
760
- app.post("/usernames/claim", async (c) => {
760
+ app.get("/users/:username", async (c) => {
761
761
  try {
762
+ const username = c.req.param("username");
763
+ const claimed = await storage.getUsername(username);
764
+ if (!claimed) {
765
+ return c.json({
766
+ username,
767
+ available: true
768
+ }, 200);
769
+ }
770
+ return c.json({
771
+ username: claimed.username,
772
+ available: false,
773
+ claimedAt: claimed.claimedAt,
774
+ expiresAt: claimed.expiresAt,
775
+ publicKey: claimed.publicKey
776
+ }, 200);
777
+ } catch (err2) {
778
+ console.error("Error checking username:", err2);
779
+ return c.json({ error: "Internal server error" }, 500);
780
+ }
781
+ });
782
+ app.post("/users/:username", async (c) => {
783
+ try {
784
+ const username = c.req.param("username");
762
785
  const body = await c.req.json();
763
- const { username, publicKey, signature, message } = body;
764
- if (!username || !publicKey || !signature || !message) {
765
- return c.json({ error: "Missing required parameters: username, publicKey, signature, message" }, 400);
786
+ const { publicKey, signature, message } = body;
787
+ if (!publicKey || !signature || !message) {
788
+ return c.json({ error: "Missing required parameters: publicKey, signature, message" }, 400);
766
789
  }
767
790
  const validation = await validateUsernameClaim(username, publicKey, signature, message);
768
791
  if (!validation.valid) {
@@ -779,7 +802,7 @@ function createApp(storage, config) {
779
802
  username: claimed.username,
780
803
  claimedAt: claimed.claimedAt,
781
804
  expiresAt: claimed.expiresAt
782
- }, 200);
805
+ }, 201);
783
806
  } catch (err2) {
784
807
  if (err2.message?.includes("already claimed")) {
785
808
  return c.json({ error: "Username already claimed by different public key" }, 409);
@@ -791,47 +814,70 @@ function createApp(storage, config) {
791
814
  return c.json({ error: "Internal server error" }, 500);
792
815
  }
793
816
  });
794
- app.get("/usernames/:username", async (c) => {
817
+ app.get("/users/:username/services", async (c) => {
795
818
  try {
796
819
  const username = c.req.param("username");
797
- const claimed = await storage.getUsername(username);
798
- if (!claimed) {
799
- return c.json({
800
- username,
801
- available: true
802
- }, 200);
803
- }
820
+ const services = await storage.listServicesForUsername(username);
804
821
  return c.json({
805
- username: claimed.username,
806
- available: false,
807
- claimedAt: claimed.claimedAt,
808
- expiresAt: claimed.expiresAt,
809
- publicKey: claimed.publicKey
822
+ username,
823
+ services
810
824
  }, 200);
811
825
  } catch (err2) {
812
- console.error("Error checking username:", err2);
826
+ console.error("Error listing services:", err2);
813
827
  return c.json({ error: "Internal server error" }, 500);
814
828
  }
815
829
  });
816
- app.get("/usernames/:username/services", async (c) => {
830
+ app.get("/users/:username/services/:fqn", async (c) => {
817
831
  try {
818
832
  const username = c.req.param("username");
819
- const services = await storage.listServicesForUsername(username);
833
+ const serviceFqn = decodeURIComponent(c.req.param("fqn"));
834
+ const uuid = await storage.queryService(username, serviceFqn);
835
+ if (!uuid) {
836
+ return c.json({ error: "Service not found" }, 404);
837
+ }
838
+ const service = await storage.getServiceByUuid(uuid);
839
+ if (!service) {
840
+ return c.json({ error: "Service not found" }, 404);
841
+ }
842
+ const initialOffer = await storage.getOfferById(service.offerId);
843
+ if (!initialOffer) {
844
+ return c.json({ error: "Associated offer not found" }, 404);
845
+ }
846
+ const peerOffers = await storage.getOffersByPeerId(initialOffer.peerId);
847
+ const availableOffer = peerOffers.find((offer) => !offer.answererPeerId);
848
+ if (!availableOffer) {
849
+ return c.json({
850
+ error: "No available offers",
851
+ message: "All offers from this service are currently in use. Please try again later."
852
+ }, 503);
853
+ }
820
854
  return c.json({
821
- username,
822
- services
855
+ uuid,
856
+ serviceId: service.id,
857
+ username: service.username,
858
+ serviceFqn: service.serviceFqn,
859
+ offerId: availableOffer.id,
860
+ sdp: availableOffer.sdp,
861
+ isPublic: service.isPublic,
862
+ metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
863
+ createdAt: service.createdAt,
864
+ expiresAt: service.expiresAt
823
865
  }, 200);
824
866
  } catch (err2) {
825
- console.error("Error listing services:", err2);
867
+ console.error("Error getting service:", err2);
826
868
  return c.json({ error: "Internal server error" }, 500);
827
869
  }
828
870
  });
829
- app.post("/services", authMiddleware, async (c) => {
871
+ app.post("/users/:username/services", authMiddleware, async (c) => {
872
+ let serviceFqn;
873
+ let offers = [];
830
874
  try {
875
+ const username = c.req.param("username");
831
876
  const body = await c.req.json();
832
- const { username, serviceFqn, sdp, ttl, isPublic, metadata, signature, message } = body;
833
- if (!username || !serviceFqn || !sdp) {
834
- return c.json({ error: "Missing required parameters: username, serviceFqn, sdp" }, 400);
877
+ serviceFqn = body.serviceFqn;
878
+ const { sdp, ttl, isPublic, metadata, signature, message } = body;
879
+ if (!serviceFqn || !sdp) {
880
+ return c.json({ error: "Missing required parameters: serviceFqn, sdp" }, 400);
835
881
  }
836
882
  const fqnValidation = validateServiceFqn(serviceFqn);
837
883
  if (!fqnValidation.valid) {
@@ -848,6 +894,13 @@ function createApp(storage, config) {
848
894
  if (!signatureValidation.valid) {
849
895
  return c.json({ error: "Invalid signature for username" }, 403);
850
896
  }
897
+ const existingUuid = await storage.queryService(username, serviceFqn);
898
+ if (existingUuid) {
899
+ const existingService = await storage.getServiceByUuid(existingUuid);
900
+ if (existingService) {
901
+ await storage.deleteService(existingService.id, username);
902
+ }
903
+ }
851
904
  if (typeof sdp !== "string" || sdp.length === 0) {
852
905
  return c.json({ error: "Invalid SDP" }, 400);
853
906
  }
@@ -860,7 +913,7 @@ function createApp(storage, config) {
860
913
  config.offerMaxTtl
861
914
  );
862
915
  const expiresAt = Date.now() + offerTtl;
863
- const offers = await storage.createOffers([{
916
+ offers = await storage.createOffers([{
864
917
  peerId,
865
918
  sdp,
866
919
  expiresAt
@@ -878,52 +931,45 @@ function createApp(storage, config) {
878
931
  metadata: metadata ? JSON.stringify(metadata) : void 0
879
932
  });
880
933
  return c.json({
881
- serviceId: result.service.id,
882
934
  uuid: result.indexUuid,
935
+ serviceFqn,
936
+ username,
937
+ serviceId: result.service.id,
883
938
  offerId: offer.id,
939
+ sdp: offer.sdp,
940
+ isPublic: result.service.isPublic,
941
+ metadata,
942
+ createdAt: result.service.createdAt,
884
943
  expiresAt: result.service.expiresAt
885
944
  }, 201);
886
945
  } catch (err2) {
887
946
  console.error("Error creating service:", err2);
888
- return c.json({ error: "Internal server error" }, 500);
947
+ console.error("Error details:", {
948
+ message: err2.message,
949
+ stack: err2.stack,
950
+ username: c.req.param("username"),
951
+ serviceFqn,
952
+ offerId: offers[0]?.id
953
+ });
954
+ return c.json({
955
+ error: "Internal server error",
956
+ details: err2.message
957
+ }, 500);
889
958
  }
890
959
  });
891
- app.get("/services/:uuid", async (c) => {
960
+ app.delete("/users/:username/services/:fqn", authMiddleware, async (c) => {
892
961
  try {
893
- const uuid = c.req.param("uuid");
962
+ const username = c.req.param("username");
963
+ const serviceFqn = decodeURIComponent(c.req.param("fqn"));
964
+ const uuid = await storage.queryService(username, serviceFqn);
965
+ if (!uuid) {
966
+ return c.json({ error: "Service not found" }, 404);
967
+ }
894
968
  const service = await storage.getServiceByUuid(uuid);
895
969
  if (!service) {
896
970
  return c.json({ error: "Service not found" }, 404);
897
971
  }
898
- const offer = await storage.getOfferById(service.offerId);
899
- if (!offer) {
900
- return c.json({ error: "Associated offer not found" }, 404);
901
- }
902
- return c.json({
903
- serviceId: service.id,
904
- username: service.username,
905
- serviceFqn: service.serviceFqn,
906
- offerId: service.offerId,
907
- sdp: offer.sdp,
908
- isPublic: service.isPublic,
909
- metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
910
- createdAt: service.createdAt,
911
- expiresAt: service.expiresAt
912
- }, 200);
913
- } catch (err2) {
914
- console.error("Error getting service:", err2);
915
- return c.json({ error: "Internal server error" }, 500);
916
- }
917
- });
918
- app.delete("/services/:serviceId", authMiddleware, async (c) => {
919
- try {
920
- const serviceId = c.req.param("serviceId");
921
- const body = await c.req.json();
922
- const { username } = body;
923
- if (!username) {
924
- return c.json({ error: "Missing required parameter: username" }, 400);
925
- }
926
- const deleted = await storage.deleteService(serviceId, username);
972
+ const deleted = await storage.deleteService(service.id, username);
927
973
  if (!deleted) {
928
974
  return c.json({ error: "Service not found or not owned by this username" }, 404);
929
975
  }
@@ -933,24 +979,39 @@ function createApp(storage, config) {
933
979
  return c.json({ error: "Internal server error" }, 500);
934
980
  }
935
981
  });
936
- app.post("/index/:username/query", async (c) => {
982
+ app.get("/services/:uuid", async (c) => {
937
983
  try {
938
- const username = c.req.param("username");
939
- const body = await c.req.json();
940
- const { serviceFqn } = body;
941
- if (!serviceFqn) {
942
- return c.json({ error: "Missing required parameter: serviceFqn" }, 400);
943
- }
944
- const uuid = await storage.queryService(username, serviceFqn);
945
- if (!uuid) {
984
+ const uuid = c.req.param("uuid");
985
+ const service = await storage.getServiceByUuid(uuid);
986
+ if (!service) {
946
987
  return c.json({ error: "Service not found" }, 404);
947
988
  }
989
+ const initialOffer = await storage.getOfferById(service.offerId);
990
+ if (!initialOffer) {
991
+ return c.json({ error: "Associated offer not found" }, 404);
992
+ }
993
+ const peerOffers = await storage.getOffersByPeerId(initialOffer.peerId);
994
+ const availableOffer = peerOffers.find((offer) => !offer.answererPeerId);
995
+ if (!availableOffer) {
996
+ return c.json({
997
+ error: "No available offers",
998
+ message: "All offers from this service are currently in use. Please try again later."
999
+ }, 503);
1000
+ }
948
1001
  return c.json({
949
1002
  uuid,
950
- allowed: true
1003
+ serviceId: service.id,
1004
+ username: service.username,
1005
+ serviceFqn: service.serviceFqn,
1006
+ offerId: availableOffer.id,
1007
+ sdp: availableOffer.sdp,
1008
+ isPublic: service.isPublic,
1009
+ metadata: service.metadata ? JSON.parse(service.metadata) : void 0,
1010
+ createdAt: service.createdAt,
1011
+ expiresAt: service.expiresAt
951
1012
  }, 200);
952
1013
  } catch (err2) {
953
- console.error("Error querying service:", err2);
1014
+ console.error("Error getting service:", err2);
954
1015
  return c.json({ error: "Internal server error" }, 500);
955
1016
  }
956
1017
  });
@@ -1020,6 +1081,28 @@ function createApp(storage, config) {
1020
1081
  return c.json({ error: "Internal server error" }, 500);
1021
1082
  }
1022
1083
  });
1084
+ app.get("/offers/:offerId", authMiddleware, async (c) => {
1085
+ try {
1086
+ const offerId = c.req.param("offerId");
1087
+ const offer = await storage.getOfferById(offerId);
1088
+ if (!offer) {
1089
+ return c.json({ error: "Offer not found" }, 404);
1090
+ }
1091
+ return c.json({
1092
+ id: offer.id,
1093
+ peerId: offer.peerId,
1094
+ sdp: offer.sdp,
1095
+ createdAt: offer.createdAt,
1096
+ expiresAt: offer.expiresAt,
1097
+ answererPeerId: offer.answererPeerId,
1098
+ answered: !!offer.answererPeerId,
1099
+ answerSdp: offer.answerSdp
1100
+ }, 200);
1101
+ } catch (err2) {
1102
+ console.error("Error getting offer:", err2);
1103
+ return c.json({ error: "Internal server error" }, 500);
1104
+ }
1105
+ });
1023
1106
  app.delete("/offers/:offerId", authMiddleware, async (c) => {
1024
1107
  try {
1025
1108
  const offerId = c.req.param("offerId");
@@ -1059,20 +1142,28 @@ function createApp(storage, config) {
1059
1142
  return c.json({ error: "Internal server error" }, 500);
1060
1143
  }
1061
1144
  });
1062
- app.get("/offers/answers", authMiddleware, async (c) => {
1145
+ app.get("/offers/:offerId/answer", authMiddleware, async (c) => {
1063
1146
  try {
1147
+ const offerId = c.req.param("offerId");
1064
1148
  const peerId = getAuthenticatedPeerId(c);
1065
- const offers = await storage.getAnsweredOffers(peerId);
1149
+ const offer = await storage.getOfferById(offerId);
1150
+ if (!offer) {
1151
+ return c.json({ error: "Offer not found" }, 404);
1152
+ }
1153
+ if (offer.peerId !== peerId) {
1154
+ return c.json({ error: "Not authorized to view this answer" }, 403);
1155
+ }
1156
+ if (!offer.answererPeerId || !offer.answerSdp) {
1157
+ return c.json({ error: "Offer not yet answered" }, 404);
1158
+ }
1066
1159
  return c.json({
1067
- answers: offers.map((offer) => ({
1068
- offerId: offer.id,
1069
- answererPeerId: offer.answererPeerId,
1070
- answerSdp: offer.answerSdp,
1071
- answeredAt: offer.answeredAt
1072
- }))
1160
+ offerId: offer.id,
1161
+ answererId: offer.answererPeerId,
1162
+ sdp: offer.answerSdp,
1163
+ answeredAt: offer.answeredAt
1073
1164
  }, 200);
1074
1165
  } catch (err2) {
1075
- console.error("Error getting answers:", err2);
1166
+ console.error("Error getting answer:", err2);
1076
1167
  return c.json({ error: "Internal server error" }, 500);
1077
1168
  }
1078
1169
  });
@@ -1143,8 +1234,7 @@ function loadConfig() {
1143
1234
  offerMaxTtl: parseInt(process.env.OFFER_MAX_TTL || "86400000", 10),
1144
1235
  offerMinTtl: parseInt(process.env.OFFER_MIN_TTL || "60000", 10),
1145
1236
  cleanupInterval: parseInt(process.env.CLEANUP_INTERVAL || "60000", 10),
1146
- maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10),
1147
- maxTopicsPerOffer: parseInt(process.env.MAX_TOPICS_PER_OFFER || "50", 10)
1237
+ maxOffersPerRequest: parseInt(process.env.MAX_OFFERS_PER_REQUEST || "100", 10)
1148
1238
  };
1149
1239
  }
1150
1240
 
@@ -1153,11 +1243,9 @@ var import_better_sqlite3 = __toESM(require("better-sqlite3"));
1153
1243
  var import_node_crypto = require("node:crypto");
1154
1244
 
1155
1245
  // src/storage/hash-id.ts
1156
- async function generateOfferHash(sdp, topics) {
1246
+ async function generateOfferHash(sdp) {
1157
1247
  const sanitizedOffer = {
1158
- sdp,
1159
- topics: [...topics].sort()
1160
- // Sort topics for consistency
1248
+ sdp
1161
1249
  };
1162
1250
  const jsonString = JSON.stringify(sanitizedOffer);
1163
1251
  const encoder = new TextEncoder();
@@ -1184,10 +1272,11 @@ var SQLiteStorage = class {
1184
1272
  */
1185
1273
  initializeDatabase() {
1186
1274
  this.db.exec(`
1187
- -- Offers table (no topics)
1275
+ -- WebRTC signaling offers
1188
1276
  CREATE TABLE IF NOT EXISTS offers (
1189
1277
  id TEXT PRIMARY KEY,
1190
1278
  peer_id TEXT NOT NULL,
1279
+ service_id TEXT,
1191
1280
  sdp TEXT NOT NULL,
1192
1281
  created_at INTEGER NOT NULL,
1193
1282
  expires_at INTEGER NOT NULL,
@@ -1195,10 +1284,12 @@ var SQLiteStorage = class {
1195
1284
  secret TEXT,
1196
1285
  answerer_peer_id TEXT,
1197
1286
  answer_sdp TEXT,
1198
- answered_at INTEGER
1287
+ answered_at INTEGER,
1288
+ FOREIGN KEY (service_id) REFERENCES services(id) ON DELETE CASCADE
1199
1289
  );
1200
1290
 
1201
1291
  CREATE INDEX IF NOT EXISTS idx_offers_peer ON offers(peer_id);
1292
+ CREATE INDEX IF NOT EXISTS idx_offers_service ON offers(service_id);
1202
1293
  CREATE INDEX IF NOT EXISTS idx_offers_expires ON offers(expires_at);
1203
1294
  CREATE INDEX IF NOT EXISTS idx_offers_last_seen ON offers(last_seen);
1204
1295
  CREATE INDEX IF NOT EXISTS idx_offers_answerer ON offers(answerer_peer_id);
@@ -1232,25 +1323,22 @@ var SQLiteStorage = class {
1232
1323
  CREATE INDEX IF NOT EXISTS idx_usernames_expires ON usernames(expires_at);
1233
1324
  CREATE INDEX IF NOT EXISTS idx_usernames_public_key ON usernames(public_key);
1234
1325
 
1235
- -- Services table
1326
+ -- Services table (one service can have multiple offers)
1236
1327
  CREATE TABLE IF NOT EXISTS services (
1237
1328
  id TEXT PRIMARY KEY,
1238
1329
  username TEXT NOT NULL,
1239
1330
  service_fqn TEXT NOT NULL,
1240
- offer_id TEXT NOT NULL,
1241
1331
  created_at INTEGER NOT NULL,
1242
1332
  expires_at INTEGER NOT NULL,
1243
1333
  is_public INTEGER NOT NULL DEFAULT 0,
1244
1334
  metadata TEXT,
1245
1335
  FOREIGN KEY (username) REFERENCES usernames(username) ON DELETE CASCADE,
1246
- FOREIGN KEY (offer_id) REFERENCES offers(id) ON DELETE CASCADE,
1247
1336
  UNIQUE(username, service_fqn)
1248
1337
  );
1249
1338
 
1250
1339
  CREATE INDEX IF NOT EXISTS idx_services_username ON services(username);
1251
1340
  CREATE INDEX IF NOT EXISTS idx_services_fqn ON services(service_fqn);
1252
1341
  CREATE INDEX IF NOT EXISTS idx_services_expires ON services(expires_at);
1253
- CREATE INDEX IF NOT EXISTS idx_services_offer ON services(offer_id);
1254
1342
 
1255
1343
  -- Service index table (privacy layer)
1256
1344
  CREATE TABLE IF NOT EXISTS service_index (
@@ -1274,19 +1362,21 @@ var SQLiteStorage = class {
1274
1362
  const offersWithIds = await Promise.all(
1275
1363
  offers.map(async (offer) => ({
1276
1364
  ...offer,
1277
- id: offer.id || await generateOfferHash(offer.sdp, [])
1365
+ serviceId: offer.serviceId || null,
1366
+ id: offer.id || await generateOfferHash(offer.sdp)
1278
1367
  }))
1279
1368
  );
1280
1369
  const transaction = this.db.transaction((offersWithIds2) => {
1281
1370
  const offerStmt = this.db.prepare(`
1282
- INSERT INTO offers (id, peer_id, sdp, created_at, expires_at, last_seen, secret)
1283
- VALUES (?, ?, ?, ?, ?, ?, ?)
1371
+ INSERT INTO offers (id, peer_id, service_id, sdp, created_at, expires_at, last_seen, secret)
1372
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1284
1373
  `);
1285
1374
  for (const offer of offersWithIds2) {
1286
1375
  const now = Date.now();
1287
1376
  offerStmt.run(
1288
1377
  offer.id,
1289
1378
  offer.peerId,
1379
+ offer.serviceId || null,
1290
1380
  offer.sdp,
1291
1381
  now,
1292
1382
  offer.expiresAt,
@@ -1296,6 +1386,7 @@ var SQLiteStorage = class {
1296
1386
  created.push({
1297
1387
  id: offer.id,
1298
1388
  peerId: offer.peerId,
1389
+ serviceId: offer.serviceId,
1299
1390
  sdp: offer.sdp,
1300
1391
  createdAt: now,
1301
1392
  expiresAt: offer.expiresAt,
@@ -1498,16 +1589,20 @@ var SQLiteStorage = class {
1498
1589
  const serviceId = (0, import_node_crypto.randomUUID)();
1499
1590
  const indexUuid = (0, import_node_crypto.randomUUID)();
1500
1591
  const now = Date.now();
1592
+ const offerRequests = request.offers.map((offer) => ({
1593
+ ...offer,
1594
+ serviceId
1595
+ }));
1596
+ const offers = await this.createOffers(offerRequests);
1501
1597
  const transaction = this.db.transaction(() => {
1502
1598
  const serviceStmt = this.db.prepare(`
1503
- INSERT INTO services (id, username, service_fqn, offer_id, created_at, expires_at, is_public, metadata)
1504
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)
1599
+ INSERT INTO services (id, username, service_fqn, created_at, expires_at, is_public, metadata)
1600
+ VALUES (?, ?, ?, ?, ?, ?, ?)
1505
1601
  `);
1506
1602
  serviceStmt.run(
1507
1603
  serviceId,
1508
1604
  request.username,
1509
1605
  request.serviceFqn,
1510
- request.offerId,
1511
1606
  now,
1512
1607
  request.expiresAt,
1513
1608
  request.isPublic ? 1 : 0,
@@ -1533,15 +1628,23 @@ var SQLiteStorage = class {
1533
1628
  id: serviceId,
1534
1629
  username: request.username,
1535
1630
  serviceFqn: request.serviceFqn,
1536
- offerId: request.offerId,
1537
1631
  createdAt: now,
1538
1632
  expiresAt: request.expiresAt,
1539
1633
  isPublic: request.isPublic || false,
1540
1634
  metadata: request.metadata
1541
1635
  },
1542
- indexUuid
1636
+ indexUuid,
1637
+ offers
1543
1638
  };
1544
1639
  }
1640
+ async batchCreateServices(requests) {
1641
+ const results = [];
1642
+ for (const request of requests) {
1643
+ const result = await this.createService(request);
1644
+ results.push(result);
1645
+ }
1646
+ return results;
1647
+ }
1545
1648
  async getServiceById(serviceId) {
1546
1649
  const stmt = this.db.prepare(`
1547
1650
  SELECT * FROM services
@@ -1614,6 +1717,7 @@ var SQLiteStorage = class {
1614
1717
  return {
1615
1718
  id: row.id,
1616
1719
  peerId: row.peer_id,
1720
+ serviceId: row.service_id || void 0,
1617
1721
  sdp: row.sdp,
1618
1722
  createdAt: row.created_at,
1619
1723
  expiresAt: row.expires_at,
@@ -1632,13 +1736,24 @@ var SQLiteStorage = class {
1632
1736
  id: row.id,
1633
1737
  username: row.username,
1634
1738
  serviceFqn: row.service_fqn,
1635
- offerId: row.offer_id,
1636
1739
  createdAt: row.created_at,
1637
1740
  expiresAt: row.expires_at,
1638
1741
  isPublic: row.is_public === 1,
1639
1742
  metadata: row.metadata || void 0
1640
1743
  };
1641
1744
  }
1745
+ /**
1746
+ * Get all offers for a service
1747
+ */
1748
+ async getOffersForService(serviceId) {
1749
+ const stmt = this.db.prepare(`
1750
+ SELECT * FROM offers
1751
+ WHERE service_id = ? AND expires_at > ?
1752
+ ORDER BY created_at ASC
1753
+ `);
1754
+ const rows = stmt.all(serviceId, Date.now());
1755
+ return rows.map((row) => this.rowToOffer(row));
1756
+ }
1642
1757
  };
1643
1758
 
1644
1759
  // src/index.ts
@@ -1654,7 +1769,6 @@ async function main() {
1654
1769
  offerMinTtl: `${config.offerMinTtl}ms`,
1655
1770
  cleanupInterval: `${config.cleanupInterval}ms`,
1656
1771
  maxOffersPerRequest: config.maxOffersPerRequest,
1657
- maxTopicsPerOffer: config.maxTopicsPerOffer,
1658
1772
  corsOrigins: config.corsOrigins,
1659
1773
  version: config.version
1660
1774
  });