@exulu/backend 0.1.9 → 0.2.1

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 (3) hide show
  1. package/dist/index.cjs +116 -119
  2. package/dist/index.js +116 -119
  3. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -59,18 +59,19 @@ var redisServer = {
59
59
  var client = {};
60
60
  async function redisClient() {
61
61
  if (!redisServer.host || !redisServer.port) {
62
- return {
63
- client: null
64
- };
62
+ return { client: null };
65
63
  }
66
64
  if (!client["exulu"]) {
67
- const url = `redis://${redisServer.host}:${redisServer.port}`;
68
- console.log(`[EXULU] connecting to redis.`);
69
- client["exulu"] = (0, import_redis.createClient)({
70
- // todo add password
71
- url
72
- });
73
- await client["exulu"].connect();
65
+ try {
66
+ const url = `redis://${redisServer.host}:${redisServer.port}`;
67
+ client["exulu"] = (0, import_redis.createClient)({
68
+ url
69
+ });
70
+ await client["exulu"].connect();
71
+ } catch (error) {
72
+ console.error(`[EXULU] error connecting to redis:`, error);
73
+ return { client: null };
74
+ }
74
75
  }
75
76
  return {
76
77
  client: client["exulu"]
@@ -113,25 +114,30 @@ var import_knex3 = require("pgvector/knex");
113
114
  var db = {};
114
115
  async function postgresClient() {
115
116
  if (!db["exulu"]) {
116
- console.log("[EXULU] Initializing exulu database.");
117
- console.log(process.env.POSTGRES_DB_HOST);
118
- console.log(process.env.POSTGRES_DB_PORT);
119
- console.log(process.env.POSTGRES_DB_USER);
120
- console.log(process.env.POSTGRES_DB_PASSWORD);
121
- console.log(process.env.POSTGRES_DB_SSL);
122
- const knex = (0, import_knex.default)({
123
- client: "pg",
124
- connection: {
125
- host: process.env.POSTGRES_DB_HOST,
126
- port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
127
- user: process.env.POSTGRES_DB_USER,
128
- database: "exulu",
129
- password: process.env.POSTGRES_DB_PASSWORD,
130
- ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
131
- }
132
- });
133
- await knex.schema.createExtensionIfNotExists("vector");
134
- db["exulu"] = knex;
117
+ try {
118
+ console.log("[EXULU] Initializing exulu database.");
119
+ console.log(process.env.POSTGRES_DB_HOST);
120
+ console.log(process.env.POSTGRES_DB_PORT);
121
+ console.log(process.env.POSTGRES_DB_USER);
122
+ console.log(process.env.POSTGRES_DB_PASSWORD);
123
+ console.log(process.env.POSTGRES_DB_SSL);
124
+ const knex = (0, import_knex.default)({
125
+ client: "pg",
126
+ connection: {
127
+ host: process.env.POSTGRES_DB_HOST,
128
+ port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
129
+ user: process.env.POSTGRES_DB_USER,
130
+ database: "exulu",
131
+ password: process.env.POSTGRES_DB_PASSWORD,
132
+ ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
133
+ }
134
+ });
135
+ await knex.schema.createExtensionIfNotExists("vector");
136
+ db["exulu"] = knex;
137
+ } catch (error) {
138
+ console.error("[EXULU] Error initializing exulu database.", error);
139
+ throw error;
140
+ }
135
141
  }
136
142
  return {
137
143
  db: db["exulu"]
@@ -547,8 +553,8 @@ var up = async function(knex) {
547
553
  }
548
554
  };
549
555
  var SALT_ROUNDS = 12;
550
- async function encryptApiKey(apiKey) {
551
- const hash = await import_bcryptjs.default.hash(apiKey, SALT_ROUNDS);
556
+ async function encryptApiKey(apikey) {
557
+ const hash = await import_bcryptjs.default.hash(apikey, SALT_ROUNDS);
552
558
  return hash;
553
559
  }
554
560
  var execute = async () => {
@@ -596,7 +602,7 @@ var execute = async () => {
596
602
  super_admin: true,
597
603
  createdAt: /* @__PURE__ */ new Date(),
598
604
  updatedAt: /* @__PURE__ */ new Date(),
599
- type: "user",
605
+ type: "api",
600
606
  apikey: `${encryptedKey}${postFix}`,
601
607
  // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
602
608
  role: roleId
@@ -1424,30 +1430,38 @@ var import_express3 = require("express");
1424
1430
 
1425
1431
  // src/registry/rate-limiter.ts
1426
1432
  var rateLimiter = async (key, windowSeconds, limit, points) => {
1427
- const { client: client2 } = await redisClient();
1428
- if (!client2) {
1433
+ try {
1434
+ const { client: client2 } = await redisClient();
1435
+ if (!client2) {
1436
+ console.warn("[EXULU] Rate limiting disabled - Redis not available");
1437
+ return {
1438
+ status: true,
1439
+ retryAfter: null
1440
+ };
1441
+ }
1442
+ const redisKey = `exulu/${key}`;
1443
+ const current = await client2.incrBy(redisKey, points);
1444
+ if (current === points) {
1445
+ await client2.expire(redisKey, windowSeconds);
1446
+ }
1447
+ if (current > limit) {
1448
+ const ttl = await client2.ttl(redisKey);
1449
+ return {
1450
+ status: false,
1451
+ retryAfter: ttl
1452
+ };
1453
+ }
1429
1454
  return {
1430
- status: false,
1431
- retryAfter: 10
1432
- // 10 seconds
1455
+ status: true,
1456
+ retryAfter: null
1433
1457
  };
1434
- }
1435
- const redisKey = `exulu/${key}`;
1436
- const current = await client2.incrBy(redisKey, points);
1437
- if (current === points) {
1438
- await client2.expire(redisKey, windowSeconds);
1439
- }
1440
- if (current > limit) {
1441
- const ttl = await client2.ttl(redisKey);
1458
+ } catch (error) {
1459
+ console.error("[EXULU] Rate limiting error:", error);
1442
1460
  return {
1443
- status: false,
1444
- retryAfter: ttl
1461
+ status: true,
1462
+ retryAfter: null
1445
1463
  };
1446
1464
  }
1447
- return {
1448
- status: true,
1449
- retryAfter: null
1450
- };
1451
1465
  };
1452
1466
 
1453
1467
  // src/registry/route-validators/index.ts
@@ -1546,10 +1560,10 @@ var authentication = async ({
1546
1560
  code: 401
1547
1561
  };
1548
1562
  }
1549
- const filtered = users.filter(({ apiKey, id }) => apiKey.includes(keyName));
1563
+ const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(keyName));
1550
1564
  for (const user of filtered) {
1551
- const lastSlashIndex = user.apiKey.lastIndexOf("/");
1552
- const compareValue = lastSlashIndex !== -1 ? user.apiKey.substring(0, lastSlashIndex) : user.apiKey;
1565
+ const lastSlashIndex = user.apikey.lastIndexOf("/");
1566
+ const compareValue = lastSlashIndex !== -1 ? user.apikey.substring(0, lastSlashIndex) : user.apikey;
1553
1567
  const isMatch = await import_bcryptjs2.default.compare(keyValue, compareValue);
1554
1568
  if (isMatch) {
1555
1569
  await db2.from("users").where({ id: user.id }).update({
@@ -1741,6 +1755,10 @@ var ExuluQueues = class {
1741
1755
  if (existing) {
1742
1756
  return existing;
1743
1757
  }
1758
+ if (!redisServer.host?.length || !redisServer.port?.length) {
1759
+ console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or workflow (look for ExuluQueues.use() ).`);
1760
+ throw new Error(`[EXULU] no redis server configured.`);
1761
+ }
1744
1762
  const newQueue = new import_bullmq5.Queue(`${name}`, { connection: redisServer });
1745
1763
  this.queues.push(newQueue);
1746
1764
  return newQueue;
@@ -2160,9 +2178,6 @@ var createUppyRoutes = async (app) => {
2160
2178
  return stsClient;
2161
2179
  }
2162
2180
  app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json());
2163
- app.get("/", (req, res) => {
2164
- res.json("Exulu upload server.");
2165
- });
2166
2181
  app.get("/s3/list", async (req, res, next) => {
2167
2182
  const apikey = req.headers["exulu-api-key"] || null;
2168
2183
  let authtoken = null;
@@ -2496,6 +2511,7 @@ var createUppyRoutes = async (app) => {
2496
2511
  };
2497
2512
 
2498
2513
  // src/registry/routes.ts
2514
+ var import_utils = require("@apollo/utils.keyvaluecache");
2499
2515
  var Papa = require("papaparse");
2500
2516
  var global_queues = {
2501
2517
  logs_cleaner: "logs-cleaner"
@@ -2589,10 +2605,21 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2589
2605
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
2590
2606
  { route: "/graphql", method: "POST", note: "GraphQL endpoint" }
2591
2607
  );
2592
- await createRecurringJobs();
2608
+ if (redisServer.host?.length && redisServer.port?.length) {
2609
+ await createRecurringJobs();
2610
+ } else {
2611
+ console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2612
+ }
2593
2613
  const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2594
- const server = new import_server3.ApolloServer({ schema, introspection: true });
2614
+ console.log("[EXULU] graphql server");
2615
+ const server = new import_server3.ApolloServer({
2616
+ cache: new import_utils.InMemoryLRUCache(),
2617
+ schema,
2618
+ introspection: true
2619
+ });
2620
+ console.log("[EXULU] starting graphql server");
2595
2621
  await server.start();
2622
+ console.log("[EXULU] graphql server started");
2596
2623
  app.use(
2597
2624
  "/graphql",
2598
2625
  (0, import_cors.default)(),
@@ -2649,7 +2676,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2649
2676
  }
2650
2677
  });
2651
2678
  });
2652
- console.log("tools", tools);
2653
2679
  app.get("/tools", async (req, res) => {
2654
2680
  res.status(200).json(tools.map((tool) => ({
2655
2681
  id: tool.id,
@@ -2926,6 +2952,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2926
2952
  method: "DELETE",
2927
2953
  note: `Delete specific embedding for a context.`
2928
2954
  });
2955
+ console.log("[EXULU] delete embedding by id");
2929
2956
  app.delete(`items/:context/:id`, async (req, res) => {
2930
2957
  const id = req.params.id;
2931
2958
  if (!req.params.context) {
@@ -2957,6 +2984,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2957
2984
  message: "Embedding deleted."
2958
2985
  });
2959
2986
  });
2987
+ console.log("[EXULU] statistics timeseries");
2960
2988
  app.post("/statistics/timeseries", async (req, res) => {
2961
2989
  const authenticationResult = await requestValidators.authenticate(req);
2962
2990
  if (!authenticationResult.user?.id) {
@@ -3005,6 +3033,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3005
3033
  }
3006
3034
  });
3007
3035
  });
3036
+ console.log("[EXULU] statistics totals");
3008
3037
  app.post("/statistics/totals", async (req, res) => {
3009
3038
  const authenticationResult = await requestValidators.authenticate(req);
3010
3039
  if (!authenticationResult.user?.id) {
@@ -3033,6 +3062,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3033
3062
  }
3034
3063
  });
3035
3064
  });
3065
+ console.log("[EXULU] contexts statistics");
3036
3066
  app.get("/contexts/statistics", async (req, res) => {
3037
3067
  const authenticationResult = await requestValidators.authenticate(req);
3038
3068
  if (!authenticationResult.user?.id) {
@@ -3064,6 +3094,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3064
3094
  }
3065
3095
  });
3066
3096
  });
3097
+ console.log("[EXULU] context by id");
3067
3098
  app.get(`/contexts/:id`, async (req, res) => {
3068
3099
  const authenticationResult = await requestValidators.authenticate(req);
3069
3100
  if (!authenticationResult.user?.id) {
@@ -3109,6 +3140,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3109
3140
  // todo
3110
3141
  });
3111
3142
  });
3143
+ console.log("[EXULU] items export by context");
3112
3144
  app.get(`/items/export/:context`, async (req, res) => {
3113
3145
  if (!req.params.context) {
3114
3146
  res.status(400).json({
@@ -3137,6 +3169,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3137
3169
  const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
3138
3170
  res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
3139
3171
  });
3172
+ console.log("[EXULU] contexts get list");
3140
3173
  app.get(`/contexts`, async (req, res) => {
3141
3174
  console.log("contexts!!");
3142
3175
  const authenticationResult = await requestValidators.authenticate(req);
@@ -3166,6 +3199,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3166
3199
  }))
3167
3200
  })));
3168
3201
  });
3202
+ console.log("[EXULU] workflows get list");
3169
3203
  app.get(`/workflows`, async (req, res) => {
3170
3204
  const authenticationResult = await requestValidators.authenticate(req);
3171
3205
  if (!authenticationResult.user?.id) {
@@ -3181,6 +3215,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3181
3215
  inputSchema: workflow.inputSchema ? (0, import_zodex.zerialize)(workflow.inputSchema) : null
3182
3216
  })));
3183
3217
  });
3218
+ console.log("[EXULU] workflow by id");
3184
3219
  app.get(`/workflows/:id`, async (req, res) => {
3185
3220
  const authenticationResult = await requestValidators.authenticate(req);
3186
3221
  if (!authenticationResult.user?.id) {
@@ -3208,6 +3243,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3208
3243
  workflow: void 0
3209
3244
  });
3210
3245
  });
3246
+ console.log("[EXULU] contexts");
3211
3247
  contexts.forEach((context) => {
3212
3248
  const sources = context.sources.get();
3213
3249
  if (!Array.isArray(sources)) {
@@ -3223,66 +3259,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3223
3259
  note: `Webhook updater for ${context.name}`
3224
3260
  });
3225
3261
  app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
3226
- const { context: id } = req.params;
3227
- if (!id) {
3228
- res.status(400).json({
3229
- message: "Missing context id in request."
3230
- });
3231
- return;
3232
- }
3233
- const context2 = contexts.find((context3) => context3.id === id);
3234
- if (!context2) {
3235
- res.status(400).json({
3236
- message: `Context for provided id: ${id} not found.`
3237
- });
3238
- return;
3239
- }
3240
- if (!context2.embedder.queue) {
3241
- res.status(500).json({ detail: "No queue set for embedder." });
3242
- return;
3243
- }
3244
- const authenticationResult = await requestValidators.authenticate(req);
3245
- if (!authenticationResult.user?.id) {
3246
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3247
- return;
3248
- }
3249
- const requestValidationResult = requestValidators.embedders(req, updater.configuration);
3250
- if (requestValidationResult.error) {
3251
- res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
3252
- return;
3253
- }
3254
- const documents = await updater.fn(req.body.configuration);
3255
- const batches = [];
3256
- for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
3257
- batches.push(documents.slice(i, i + context2.embedder.batchSize));
3258
- }
3259
- let promises2 = [];
3260
- if (batches.length > 0) {
3261
- promises2 = batches.map((documents2) => {
3262
- return bullmqDecorator({
3263
- label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
3264
- type: "embedder",
3265
- embedder: context2.embedder.id,
3266
- updater: updater.id,
3267
- context: context2.id,
3268
- trigger: updater.type,
3269
- source: source.id,
3270
- inputs: req.body.inputs,
3271
- ...updater.configuration && { configuration: req.body.configuration },
3272
- documents: documents2,
3273
- queue: context2.embedder.queue,
3274
- user: authenticationResult.user.id
3275
- });
3276
- });
3277
- }
3278
- const jobs = await Promise.all(promises2);
3279
- res.status(200).json(jobs);
3262
+ res.status(200).json([]);
3280
3263
  return;
3281
3264
  });
3282
3265
  }
3283
3266
  });
3284
3267
  });
3285
3268
  });
3269
+ console.log("[EXULU] agents");
3286
3270
  agents.forEach((agent) => {
3287
3271
  const slug = agent.slug;
3288
3272
  if (!slug) return;
@@ -3310,6 +3294,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3310
3294
  return;
3311
3295
  }
3312
3296
  if (agent.rateLimit) {
3297
+ console.log("[EXULU] rate limiting agent.", agent.rateLimit);
3313
3298
  const limit = await rateLimiter(
3314
3299
  agent.rateLimit.name || agent.id,
3315
3300
  agent.rateLimit.rate_limit.time,
@@ -3382,6 +3367,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3382
3367
  }
3383
3368
  });
3384
3369
  });
3370
+ console.log("[EXULU] workflows");
3385
3371
  workflows.forEach((workflow) => {
3386
3372
  routeLogs.push({
3387
3373
  route: workflow.slug,
@@ -3447,7 +3433,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3447
3433
  return;
3448
3434
  });
3449
3435
  });
3450
- await createUppyRoutes(app);
3436
+ if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
3437
+ await createUppyRoutes(app);
3438
+ } else {
3439
+ console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
3440
+ }
3451
3441
  console.log("Routes:");
3452
3442
  console.table(routeLogs);
3453
3443
  };
@@ -3547,11 +3537,18 @@ var bullmq = {
3547
3537
  var fs2 = __toESM(require("fs"), 1);
3548
3538
  var import_path = __toESM(require("path"), 1);
3549
3539
  var defaultLogsDir = import_path.default.join(process.cwd(), "logs");
3550
- var redisConnection = new import_ioredis.default({
3551
- ...redisServer,
3552
- maxRetriesPerRequest: null
3553
- });
3540
+ var redisConnection;
3554
3541
  var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
3542
+ if (!redisServer.host || !redisServer.port) {
3543
+ console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
3544
+ throw new Error("No redis server configured in the environment, so cannot start workers.");
3545
+ }
3546
+ if (!redisConnection) {
3547
+ redisConnection = new import_ioredis.default({
3548
+ ...redisServer,
3549
+ maxRetriesPerRequest: null
3550
+ });
3551
+ }
3555
3552
  const logsDir = _logsDir || defaultLogsDir;
3556
3553
  const workers = queues2.map((queue) => {
3557
3554
  console.log(`[EXULU] creating worker for queue ${queue}.`);
package/dist/index.js CHANGED
@@ -18,18 +18,19 @@ var redisServer = {
18
18
  var client = {};
19
19
  async function redisClient() {
20
20
  if (!redisServer.host || !redisServer.port) {
21
- return {
22
- client: null
23
- };
21
+ return { client: null };
24
22
  }
25
23
  if (!client["exulu"]) {
26
- const url = `redis://${redisServer.host}:${redisServer.port}`;
27
- console.log(`[EXULU] connecting to redis.`);
28
- client["exulu"] = createClient({
29
- // todo add password
30
- url
31
- });
32
- await client["exulu"].connect();
24
+ try {
25
+ const url = `redis://${redisServer.host}:${redisServer.port}`;
26
+ client["exulu"] = createClient({
27
+ url
28
+ });
29
+ await client["exulu"].connect();
30
+ } catch (error) {
31
+ console.error(`[EXULU] error connecting to redis:`, error);
32
+ return { client: null };
33
+ }
33
34
  }
34
35
  return {
35
36
  client: client["exulu"]
@@ -72,25 +73,30 @@ import "pgvector/knex";
72
73
  var db = {};
73
74
  async function postgresClient() {
74
75
  if (!db["exulu"]) {
75
- console.log("[EXULU] Initializing exulu database.");
76
- console.log(process.env.POSTGRES_DB_HOST);
77
- console.log(process.env.POSTGRES_DB_PORT);
78
- console.log(process.env.POSTGRES_DB_USER);
79
- console.log(process.env.POSTGRES_DB_PASSWORD);
80
- console.log(process.env.POSTGRES_DB_SSL);
81
- const knex = Knex({
82
- client: "pg",
83
- connection: {
84
- host: process.env.POSTGRES_DB_HOST,
85
- port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
86
- user: process.env.POSTGRES_DB_USER,
87
- database: "exulu",
88
- password: process.env.POSTGRES_DB_PASSWORD,
89
- ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
90
- }
91
- });
92
- await knex.schema.createExtensionIfNotExists("vector");
93
- db["exulu"] = knex;
76
+ try {
77
+ console.log("[EXULU] Initializing exulu database.");
78
+ console.log(process.env.POSTGRES_DB_HOST);
79
+ console.log(process.env.POSTGRES_DB_PORT);
80
+ console.log(process.env.POSTGRES_DB_USER);
81
+ console.log(process.env.POSTGRES_DB_PASSWORD);
82
+ console.log(process.env.POSTGRES_DB_SSL);
83
+ const knex = Knex({
84
+ client: "pg",
85
+ connection: {
86
+ host: process.env.POSTGRES_DB_HOST,
87
+ port: parseInt(process.env.POSTGRES_DB_PORT || "5432"),
88
+ user: process.env.POSTGRES_DB_USER,
89
+ database: "exulu",
90
+ password: process.env.POSTGRES_DB_PASSWORD,
91
+ ssl: process.env.POSTGRES_DB_SSL === "true" ? { rejectUnauthorized: false } : false
92
+ }
93
+ });
94
+ await knex.schema.createExtensionIfNotExists("vector");
95
+ db["exulu"] = knex;
96
+ } catch (error) {
97
+ console.error("[EXULU] Error initializing exulu database.", error);
98
+ throw error;
99
+ }
94
100
  }
95
101
  return {
96
102
  db: db["exulu"]
@@ -506,8 +512,8 @@ var up = async function(knex) {
506
512
  }
507
513
  };
508
514
  var SALT_ROUNDS = 12;
509
- async function encryptApiKey(apiKey) {
510
- const hash = await bcrypt.hash(apiKey, SALT_ROUNDS);
515
+ async function encryptApiKey(apikey) {
516
+ const hash = await bcrypt.hash(apikey, SALT_ROUNDS);
511
517
  return hash;
512
518
  }
513
519
  var execute = async () => {
@@ -555,7 +561,7 @@ var execute = async () => {
555
561
  super_admin: true,
556
562
  createdAt: /* @__PURE__ */ new Date(),
557
563
  updatedAt: /* @__PURE__ */ new Date(),
558
- type: "user",
564
+ type: "api",
559
565
  apikey: `${encryptedKey}${postFix}`,
560
566
  // password: "admin", todo add this again when we implement password auth / encryption as alternative to OTP
561
567
  role: roleId
@@ -1383,30 +1389,38 @@ import "express";
1383
1389
 
1384
1390
  // src/registry/rate-limiter.ts
1385
1391
  var rateLimiter = async (key, windowSeconds, limit, points) => {
1386
- const { client: client2 } = await redisClient();
1387
- if (!client2) {
1392
+ try {
1393
+ const { client: client2 } = await redisClient();
1394
+ if (!client2) {
1395
+ console.warn("[EXULU] Rate limiting disabled - Redis not available");
1396
+ return {
1397
+ status: true,
1398
+ retryAfter: null
1399
+ };
1400
+ }
1401
+ const redisKey = `exulu/${key}`;
1402
+ const current = await client2.incrBy(redisKey, points);
1403
+ if (current === points) {
1404
+ await client2.expire(redisKey, windowSeconds);
1405
+ }
1406
+ if (current > limit) {
1407
+ const ttl = await client2.ttl(redisKey);
1408
+ return {
1409
+ status: false,
1410
+ retryAfter: ttl
1411
+ };
1412
+ }
1388
1413
  return {
1389
- status: false,
1390
- retryAfter: 10
1391
- // 10 seconds
1414
+ status: true,
1415
+ retryAfter: null
1392
1416
  };
1393
- }
1394
- const redisKey = `exulu/${key}`;
1395
- const current = await client2.incrBy(redisKey, points);
1396
- if (current === points) {
1397
- await client2.expire(redisKey, windowSeconds);
1398
- }
1399
- if (current > limit) {
1400
- const ttl = await client2.ttl(redisKey);
1417
+ } catch (error) {
1418
+ console.error("[EXULU] Rate limiting error:", error);
1401
1419
  return {
1402
- status: false,
1403
- retryAfter: ttl
1420
+ status: true,
1421
+ retryAfter: null
1404
1422
  };
1405
1423
  }
1406
- return {
1407
- status: true,
1408
- retryAfter: null
1409
- };
1410
1424
  };
1411
1425
 
1412
1426
  // src/registry/route-validators/index.ts
@@ -1505,10 +1519,10 @@ var authentication = async ({
1505
1519
  code: 401
1506
1520
  };
1507
1521
  }
1508
- const filtered = users.filter(({ apiKey, id }) => apiKey.includes(keyName));
1522
+ const filtered = users.filter(({ apikey: apikey2, id }) => apikey2.includes(keyName));
1509
1523
  for (const user of filtered) {
1510
- const lastSlashIndex = user.apiKey.lastIndexOf("/");
1511
- const compareValue = lastSlashIndex !== -1 ? user.apiKey.substring(0, lastSlashIndex) : user.apiKey;
1524
+ const lastSlashIndex = user.apikey.lastIndexOf("/");
1525
+ const compareValue = lastSlashIndex !== -1 ? user.apikey.substring(0, lastSlashIndex) : user.apikey;
1512
1526
  const isMatch = await bcrypt2.compare(keyValue, compareValue);
1513
1527
  if (isMatch) {
1514
1528
  await db2.from("users").where({ id: user.id }).update({
@@ -1700,6 +1714,10 @@ var ExuluQueues = class {
1700
1714
  if (existing) {
1701
1715
  return existing;
1702
1716
  }
1717
+ if (!redisServer.host?.length || !redisServer.port?.length) {
1718
+ console.error(`[EXULU] no redis server configured, but you are trying to use a queue ( ${name}), likely in an agent or workflow (look for ExuluQueues.use() ).`);
1719
+ throw new Error(`[EXULU] no redis server configured.`);
1720
+ }
1703
1721
  const newQueue = new Queue3(`${name}`, { connection: redisServer });
1704
1722
  this.queues.push(newQueue);
1705
1723
  return newQueue;
@@ -2119,9 +2137,6 @@ var createUppyRoutes = async (app) => {
2119
2137
  return stsClient;
2120
2138
  }
2121
2139
  app.use(bodyParser.urlencoded({ extended: true }), bodyParser.json());
2122
- app.get("/", (req, res) => {
2123
- res.json("Exulu upload server.");
2124
- });
2125
2140
  app.get("/s3/list", async (req, res, next) => {
2126
2141
  const apikey = req.headers["exulu-api-key"] || null;
2127
2142
  let authtoken = null;
@@ -2455,6 +2470,7 @@ var createUppyRoutes = async (app) => {
2455
2470
  };
2456
2471
 
2457
2472
  // src/registry/routes.ts
2473
+ import { InMemoryLRUCache } from "@apollo/utils.keyvaluecache";
2458
2474
  var Papa = __require("papaparse");
2459
2475
  var global_queues = {
2460
2476
  logs_cleaner: "logs-cleaner"
@@ -2548,10 +2564,21 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2548
2564
  { route: "/items/export/:context", method: "GET", note: "Export items from context" },
2549
2565
  { route: "/graphql", method: "POST", note: "GraphQL endpoint" }
2550
2566
  );
2551
- await createRecurringJobs();
2567
+ if (redisServer.host?.length && redisServer.port?.length) {
2568
+ await createRecurringJobs();
2569
+ } else {
2570
+ console.log("===========================", "[EXULU] no redis server configured, not setting up recurring jobs.", "===========================");
2571
+ }
2552
2572
  const schema = createSDL([usersSchema, rolesSchema, agentsSchema, jobsSchema]);
2553
- const server = new ApolloServer({ schema, introspection: true });
2573
+ console.log("[EXULU] graphql server");
2574
+ const server = new ApolloServer({
2575
+ cache: new InMemoryLRUCache(),
2576
+ schema,
2577
+ introspection: true
2578
+ });
2579
+ console.log("[EXULU] starting graphql server");
2554
2580
  await server.start();
2581
+ console.log("[EXULU] graphql server started");
2555
2582
  app.use(
2556
2583
  "/graphql",
2557
2584
  cors(),
@@ -2608,7 +2635,6 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2608
2635
  }
2609
2636
  });
2610
2637
  });
2611
- console.log("tools", tools);
2612
2638
  app.get("/tools", async (req, res) => {
2613
2639
  res.status(200).json(tools.map((tool) => ({
2614
2640
  id: tool.id,
@@ -2885,6 +2911,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2885
2911
  method: "DELETE",
2886
2912
  note: `Delete specific embedding for a context.`
2887
2913
  });
2914
+ console.log("[EXULU] delete embedding by id");
2888
2915
  app.delete(`items/:context/:id`, async (req, res) => {
2889
2916
  const id = req.params.id;
2890
2917
  if (!req.params.context) {
@@ -2916,6 +2943,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2916
2943
  message: "Embedding deleted."
2917
2944
  });
2918
2945
  });
2946
+ console.log("[EXULU] statistics timeseries");
2919
2947
  app.post("/statistics/timeseries", async (req, res) => {
2920
2948
  const authenticationResult = await requestValidators.authenticate(req);
2921
2949
  if (!authenticationResult.user?.id) {
@@ -2964,6 +2992,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2964
2992
  }
2965
2993
  });
2966
2994
  });
2995
+ console.log("[EXULU] statistics totals");
2967
2996
  app.post("/statistics/totals", async (req, res) => {
2968
2997
  const authenticationResult = await requestValidators.authenticate(req);
2969
2998
  if (!authenticationResult.user?.id) {
@@ -2992,6 +3021,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
2992
3021
  }
2993
3022
  });
2994
3023
  });
3024
+ console.log("[EXULU] contexts statistics");
2995
3025
  app.get("/contexts/statistics", async (req, res) => {
2996
3026
  const authenticationResult = await requestValidators.authenticate(req);
2997
3027
  if (!authenticationResult.user?.id) {
@@ -3023,6 +3053,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3023
3053
  }
3024
3054
  });
3025
3055
  });
3056
+ console.log("[EXULU] context by id");
3026
3057
  app.get(`/contexts/:id`, async (req, res) => {
3027
3058
  const authenticationResult = await requestValidators.authenticate(req);
3028
3059
  if (!authenticationResult.user?.id) {
@@ -3068,6 +3099,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3068
3099
  // todo
3069
3100
  });
3070
3101
  });
3102
+ console.log("[EXULU] items export by context");
3071
3103
  app.get(`/items/export/:context`, async (req, res) => {
3072
3104
  if (!req.params.context) {
3073
3105
  res.status(400).json({
@@ -3096,6 +3128,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3096
3128
  const ISOTime = (/* @__PURE__ */ new Date()).toISOString();
3097
3129
  res.status(200).attachment(`${context.name}-items-export-${ISOTime}.csv`).send(csv);
3098
3130
  });
3131
+ console.log("[EXULU] contexts get list");
3099
3132
  app.get(`/contexts`, async (req, res) => {
3100
3133
  console.log("contexts!!");
3101
3134
  const authenticationResult = await requestValidators.authenticate(req);
@@ -3125,6 +3158,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3125
3158
  }))
3126
3159
  })));
3127
3160
  });
3161
+ console.log("[EXULU] workflows get list");
3128
3162
  app.get(`/workflows`, async (req, res) => {
3129
3163
  const authenticationResult = await requestValidators.authenticate(req);
3130
3164
  if (!authenticationResult.user?.id) {
@@ -3140,6 +3174,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3140
3174
  inputSchema: workflow.inputSchema ? zerialize(workflow.inputSchema) : null
3141
3175
  })));
3142
3176
  });
3177
+ console.log("[EXULU] workflow by id");
3143
3178
  app.get(`/workflows/:id`, async (req, res) => {
3144
3179
  const authenticationResult = await requestValidators.authenticate(req);
3145
3180
  if (!authenticationResult.user?.id) {
@@ -3167,6 +3202,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3167
3202
  workflow: void 0
3168
3203
  });
3169
3204
  });
3205
+ console.log("[EXULU] contexts");
3170
3206
  contexts.forEach((context) => {
3171
3207
  const sources = context.sources.get();
3172
3208
  if (!Array.isArray(sources)) {
@@ -3182,66 +3218,14 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3182
3218
  note: `Webhook updater for ${context.name}`
3183
3219
  });
3184
3220
  app.post(`${updater.slug}/${updater.type}/:context`, async (req, res) => {
3185
- const { context: id } = req.params;
3186
- if (!id) {
3187
- res.status(400).json({
3188
- message: "Missing context id in request."
3189
- });
3190
- return;
3191
- }
3192
- const context2 = contexts.find((context3) => context3.id === id);
3193
- if (!context2) {
3194
- res.status(400).json({
3195
- message: `Context for provided id: ${id} not found.`
3196
- });
3197
- return;
3198
- }
3199
- if (!context2.embedder.queue) {
3200
- res.status(500).json({ detail: "No queue set for embedder." });
3201
- return;
3202
- }
3203
- const authenticationResult = await requestValidators.authenticate(req);
3204
- if (!authenticationResult.user?.id) {
3205
- res.status(authenticationResult.code || 500).json({ detail: `${authenticationResult.message}` });
3206
- return;
3207
- }
3208
- const requestValidationResult = requestValidators.embedders(req, updater.configuration);
3209
- if (requestValidationResult.error) {
3210
- res.status(requestValidationResult.code || 500).json({ detail: `${requestValidationResult.message}` });
3211
- return;
3212
- }
3213
- const documents = await updater.fn(req.body.configuration);
3214
- const batches = [];
3215
- for (let i = 0; i < documents.length; i += context2.embedder.batchSize) {
3216
- batches.push(documents.slice(i, i + context2.embedder.batchSize));
3217
- }
3218
- let promises2 = [];
3219
- if (batches.length > 0) {
3220
- promises2 = batches.map((documents2) => {
3221
- return bullmqDecorator({
3222
- label: `Job running context '${context2.name}' with embedder '${context2.embedder.name}' for '${req.body.label}'`,
3223
- type: "embedder",
3224
- embedder: context2.embedder.id,
3225
- updater: updater.id,
3226
- context: context2.id,
3227
- trigger: updater.type,
3228
- source: source.id,
3229
- inputs: req.body.inputs,
3230
- ...updater.configuration && { configuration: req.body.configuration },
3231
- documents: documents2,
3232
- queue: context2.embedder.queue,
3233
- user: authenticationResult.user.id
3234
- });
3235
- });
3236
- }
3237
- const jobs = await Promise.all(promises2);
3238
- res.status(200).json(jobs);
3221
+ res.status(200).json([]);
3239
3222
  return;
3240
3223
  });
3241
3224
  }
3242
3225
  });
3243
3226
  });
3244
3227
  });
3228
+ console.log("[EXULU] agents");
3245
3229
  agents.forEach((agent) => {
3246
3230
  const slug = agent.slug;
3247
3231
  if (!slug) return;
@@ -3269,6 +3253,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3269
3253
  return;
3270
3254
  }
3271
3255
  if (agent.rateLimit) {
3256
+ console.log("[EXULU] rate limiting agent.", agent.rateLimit);
3272
3257
  const limit = await rateLimiter(
3273
3258
  agent.rateLimit.name || agent.id,
3274
3259
  agent.rateLimit.rate_limit.time,
@@ -3341,6 +3326,7 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3341
3326
  }
3342
3327
  });
3343
3328
  });
3329
+ console.log("[EXULU] workflows");
3344
3330
  workflows.forEach((workflow) => {
3345
3331
  routeLogs.push({
3346
3332
  route: workflow.slug,
@@ -3406,7 +3392,11 @@ var createExpressRoutes = async (app, agents, embedders, tools, workflows, conte
3406
3392
  return;
3407
3393
  });
3408
3394
  });
3409
- await createUppyRoutes(app);
3395
+ if (process.env.COMPANION_S3_REGION && process.env.COMPANION_S3_KEY && process.env.COMPANION_S3_SECRET) {
3396
+ await createUppyRoutes(app);
3397
+ } else {
3398
+ console.log("[EXULU] skipping uppy file upload routes, because no S3 compatible region, key or secret is set in the environment.");
3399
+ }
3410
3400
  console.log("Routes:");
3411
3401
  console.table(routeLogs);
3412
3402
  };
@@ -3506,11 +3496,18 @@ var bullmq = {
3506
3496
  import * as fs2 from "fs";
3507
3497
  import path2 from "path";
3508
3498
  var defaultLogsDir = path2.join(process.cwd(), "logs");
3509
- var redisConnection = new IORedis({
3510
- ...redisServer,
3511
- maxRetriesPerRequest: null
3512
- });
3499
+ var redisConnection;
3513
3500
  var createWorkers = async (queues2, contexts, embedders, workflows, _logsDir) => {
3501
+ if (!redisServer.host || !redisServer.port) {
3502
+ console.error("[EXULU] you are trying to start workers, but no redis server is configured in the environment.");
3503
+ throw new Error("No redis server configured in the environment, so cannot start workers.");
3504
+ }
3505
+ if (!redisConnection) {
3506
+ redisConnection = new IORedis({
3507
+ ...redisServer,
3508
+ maxRetriesPerRequest: null
3509
+ });
3510
+ }
3514
3511
  const logsDir = _logsDir || defaultLogsDir;
3515
3512
  const workers = queues2.map((queue) => {
3516
3513
  console.log(`[EXULU] creating worker for queue ${queue}.`);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "0.1.9",
4
+ "version": "0.2.1",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {