@exulu/backend 1.31.2 → 1.32.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/CHANGELOG.md CHANGED
@@ -1,6 +1,6 @@
1
- ## [1.31.2](https://github.com/Qventu/exulu-backend/compare/v1.31.1...v1.31.2) (2025-11-10)
1
+ # [1.32.0](https://github.com/Qventu/exulu-backend/compare/v1.31.2...v1.32.0) (2025-11-11)
2
2
 
3
3
 
4
- ### Bug Fixes
4
+ ### Features
5
5
 
6
- * minor exulu upgrade for redis connection ([7d20243](https://github.com/Qventu/exulu-backend/commit/7d2024397562e2feff9f3515eafbc46fcfd0ab94))
6
+ * implement scheduled data sources for contexts ([d936754](https://github.com/Qventu/exulu-backend/commit/d9367545fc3dc35b9c2d2ad0820fc7d2fddd4666))
package/dist/index.cjs CHANGED
@@ -2334,7 +2334,7 @@ function createMutations(table, agents, contexts, tools, config) {
2334
2334
  if (!item) {
2335
2335
  throw new Error("Item not found, or your user does not have access to it.");
2336
2336
  }
2337
- const { job, result } = await exists.process(
2337
+ const { job, result } = await exists.processField(
2338
2338
  "api",
2339
2339
  context.user.id,
2340
2340
  context.user.role?.id,
@@ -5380,10 +5380,12 @@ var ExuluContext = class {
5380
5380
  resultReranker;
5381
5381
  // todo typings
5382
5382
  configuration;
5383
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration }) {
5383
+ sources = [];
5384
+ constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }) {
5384
5385
  this.id = id;
5385
5386
  this.name = name;
5386
5387
  this.fields = fields || [];
5388
+ this.sources = sources || [];
5387
5389
  this.configuration = configuration || {
5388
5390
  calculateVectors: "manual",
5389
5391
  language: "english",
@@ -5396,7 +5398,7 @@ var ExuluContext = class {
5396
5398
  this.queryRewriter = queryRewriter;
5397
5399
  this.resultReranker = resultReranker;
5398
5400
  }
5399
- process = async (trigger, user, role, item, config) => {
5401
+ processField = async (trigger, user, role, item, config) => {
5400
5402
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5401
5403
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5402
5404
  const field = this.fields.find((field2) => field2.name === item.field?.replace("_s3key", ""));
@@ -5458,6 +5460,9 @@ var ExuluContext = class {
5458
5460
  results: []
5459
5461
  };
5460
5462
  };
5463
+ executeSource = async (source, inputs) => {
5464
+ return await source.execute(inputs);
5465
+ };
5461
5466
  tableExists = async () => {
5462
5467
  const { db: db3 } = await postgresClient();
5463
5468
  const tableName = getTableName(this.id);
@@ -6972,6 +6977,49 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6972
6977
  metadata: {}
6973
6978
  };
6974
6979
  }
6980
+ if (data.type === "source") {
6981
+ console.log("[EXULU] running a source job.", logMetadata(bullmqJob.name));
6982
+ if (!data.source) {
6983
+ throw new Error(`No source id set for source job.`);
6984
+ }
6985
+ if (!data.context) {
6986
+ throw new Error(`No context id set for source job.`);
6987
+ }
6988
+ const context = contexts.find((c) => c.id === data.context);
6989
+ if (!context) {
6990
+ throw new Error(`Context ${data.context} not found in the registry.`);
6991
+ }
6992
+ const source = context.sources.find((s) => s.id === data.source);
6993
+ if (!source) {
6994
+ throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
6995
+ }
6996
+ const result = await source.execute(data.inputs);
6997
+ let jobs = [];
6998
+ let items = [];
6999
+ for (const item of result) {
7000
+ const { item: createdItem, job } = await context.createItem(item, config, data.user, data.role);
7001
+ if (job) {
7002
+ jobs.push(job);
7003
+ console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata(bullmqJob.name, {
7004
+ item: createdItem,
7005
+ job
7006
+ }));
7007
+ }
7008
+ if (createdItem.id) {
7009
+ items.push(createdItem.id);
7010
+ console.log(`[EXULU] created item through source update job ${createdItem.id}`, logMetadata(bullmqJob.name, {
7011
+ item: createdItem
7012
+ }));
7013
+ }
7014
+ }
7015
+ return {
7016
+ result,
7017
+ metadata: {
7018
+ jobs,
7019
+ items
7020
+ }
7021
+ };
7022
+ }
6975
7023
  throw new Error(`Invalid job type: ${data.type} for job ${bullmqJob.name}.`);
6976
7024
  } catch (error) {
6977
7025
  console.error(`[EXULU] job failed.`, error instanceof Error ? error.message : String(error));
@@ -8878,6 +8926,41 @@ var ExuluApp = class {
8878
8926
  if (queues2) {
8879
8927
  filteredQueues = filteredQueues.filter((q) => queues2.includes(q.queue.name));
8880
8928
  }
8929
+ const sources = Object.values(this._contexts ?? {}).flatMap((context) => ({
8930
+ ...context.sources,
8931
+ context: context.id
8932
+ }));
8933
+ if (sources.length > 0) {
8934
+ console.log("[EXULU] Creating ContextSource schedulers for", sources.length, "sources.");
8935
+ for (const source of sources) {
8936
+ const queue = await source.config?.queue;
8937
+ if (queue) {
8938
+ if (!source.config.schedule) {
8939
+ throw new Error("Schedule is required for source when configuring a queue: " + source.name);
8940
+ }
8941
+ console.log("[EXULU] Creating ContextSource scheduler for", source.name, "in queue", queue.queue?.name);
8942
+ await queue.queue?.upsertJobScheduler(source.id, {
8943
+ pattern: source.config?.schedule
8944
+ }, {
8945
+ // default job data
8946
+ name: `${source.id}-job`,
8947
+ data: {
8948
+ source: source.id,
8949
+ context: source.context,
8950
+ type: "source"
8951
+ },
8952
+ opts: {
8953
+ backoff: {
8954
+ type: source.config.backoff?.type || "exponential",
8955
+ delay: source.config.backoff?.delay || 2e3
8956
+ },
8957
+ attempts: source.config.retries || 3,
8958
+ removeOnFail: 200
8959
+ }
8960
+ });
8961
+ }
8962
+ }
8963
+ }
8881
8964
  return await createWorkers(
8882
8965
  this._agents,
8883
8966
  filteredQueues,
package/dist/index.d.cts CHANGED
@@ -544,6 +544,21 @@ declare class ExuluStorage {
544
544
  getPresignedUrl: (key: string) => Promise<string>;
545
545
  uploadFile: (user: number, file: Buffer | Uint8Array, key: string, type: string, metadata?: Record<string, string>) => Promise<string>;
546
546
  }
547
+ type ExuluContextSource = {
548
+ id: string;
549
+ name: string;
550
+ description: string;
551
+ config: {
552
+ schedule: string;
553
+ queue: Promise<ExuluQueueConfig>;
554
+ retries?: number;
555
+ backoff?: {
556
+ type: 'exponential' | 'linear';
557
+ delay: number;
558
+ };
559
+ };
560
+ execute: (inputs: any) => Promise<Item[]>;
561
+ };
547
562
  declare class ExuluContext {
548
563
  id: string;
549
564
  name: string;
@@ -559,12 +574,14 @@ declare class ExuluContext {
559
574
  defaultRightsMode?: ExuluRightsMode;
560
575
  language?: "german" | "english";
561
576
  };
562
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration }: {
577
+ sources: ExuluContextSource[];
578
+ constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }: {
563
579
  id: string;
564
580
  name: string;
565
581
  fields: ExuluContextFieldDefinition[];
566
582
  description: string;
567
583
  embedder?: ExuluEmbedder;
584
+ sources: ExuluContextSource[];
568
585
  category?: string;
569
586
  active: boolean;
570
587
  rateLimit?: RateLimiterRule;
@@ -576,13 +593,14 @@ declare class ExuluContext {
576
593
  language?: "german" | "english";
577
594
  };
578
595
  });
579
- process: (trigger: STATISTICS_LABELS, user: number, role: string, item: Item & {
596
+ processField: (trigger: STATISTICS_LABELS, user: number, role: string, item: Item & {
580
597
  field: string;
581
598
  }, config: ExuluConfig) => Promise<{
582
599
  result: string;
583
600
  job?: string;
584
601
  }>;
585
602
  deleteAll: () => Promise<VectorOperationResponse>;
603
+ executeSource: (source: ExuluContextSource, inputs: any) => Promise<Item[]>;
586
604
  tableExists: () => Promise<boolean>;
587
605
  chunksTableExists: () => Promise<boolean>;
588
606
  createAndUpsertEmbeddings: (item: Item, config: ExuluConfig, user?: number, statistics?: ExuluStatisticParams, role?: string, job?: string) => Promise<{
package/dist/index.d.ts CHANGED
@@ -544,6 +544,21 @@ declare class ExuluStorage {
544
544
  getPresignedUrl: (key: string) => Promise<string>;
545
545
  uploadFile: (user: number, file: Buffer | Uint8Array, key: string, type: string, metadata?: Record<string, string>) => Promise<string>;
546
546
  }
547
+ type ExuluContextSource = {
548
+ id: string;
549
+ name: string;
550
+ description: string;
551
+ config: {
552
+ schedule: string;
553
+ queue: Promise<ExuluQueueConfig>;
554
+ retries?: number;
555
+ backoff?: {
556
+ type: 'exponential' | 'linear';
557
+ delay: number;
558
+ };
559
+ };
560
+ execute: (inputs: any) => Promise<Item[]>;
561
+ };
547
562
  declare class ExuluContext {
548
563
  id: string;
549
564
  name: string;
@@ -559,12 +574,14 @@ declare class ExuluContext {
559
574
  defaultRightsMode?: ExuluRightsMode;
560
575
  language?: "german" | "english";
561
576
  };
562
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration }: {
577
+ sources: ExuluContextSource[];
578
+ constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }: {
563
579
  id: string;
564
580
  name: string;
565
581
  fields: ExuluContextFieldDefinition[];
566
582
  description: string;
567
583
  embedder?: ExuluEmbedder;
584
+ sources: ExuluContextSource[];
568
585
  category?: string;
569
586
  active: boolean;
570
587
  rateLimit?: RateLimiterRule;
@@ -576,13 +593,14 @@ declare class ExuluContext {
576
593
  language?: "german" | "english";
577
594
  };
578
595
  });
579
- process: (trigger: STATISTICS_LABELS, user: number, role: string, item: Item & {
596
+ processField: (trigger: STATISTICS_LABELS, user: number, role: string, item: Item & {
580
597
  field: string;
581
598
  }, config: ExuluConfig) => Promise<{
582
599
  result: string;
583
600
  job?: string;
584
601
  }>;
585
602
  deleteAll: () => Promise<VectorOperationResponse>;
603
+ executeSource: (source: ExuluContextSource, inputs: any) => Promise<Item[]>;
586
604
  tableExists: () => Promise<boolean>;
587
605
  chunksTableExists: () => Promise<boolean>;
588
606
  createAndUpsertEmbeddings: (item: Item, config: ExuluConfig, user?: number, statistics?: ExuluStatisticParams, role?: string, job?: string) => Promise<{
package/dist/index.js CHANGED
@@ -2282,7 +2282,7 @@ function createMutations(table, agents, contexts, tools, config) {
2282
2282
  if (!item) {
2283
2283
  throw new Error("Item not found, or your user does not have access to it.");
2284
2284
  }
2285
- const { job, result } = await exists.process(
2285
+ const { job, result } = await exists.processField(
2286
2286
  "api",
2287
2287
  context.user.id,
2288
2288
  context.user.role?.id,
@@ -5347,10 +5347,12 @@ var ExuluContext = class {
5347
5347
  resultReranker;
5348
5348
  // todo typings
5349
5349
  configuration;
5350
- constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration }) {
5350
+ sources = [];
5351
+ constructor({ id, name, description, embedder, active, rateLimit, fields, queryRewriter, resultReranker, configuration, sources }) {
5351
5352
  this.id = id;
5352
5353
  this.name = name;
5353
5354
  this.fields = fields || [];
5355
+ this.sources = sources || [];
5354
5356
  this.configuration = configuration || {
5355
5357
  calculateVectors: "manual",
5356
5358
  language: "english",
@@ -5363,7 +5365,7 @@ var ExuluContext = class {
5363
5365
  this.queryRewriter = queryRewriter;
5364
5366
  this.resultReranker = resultReranker;
5365
5367
  }
5366
- process = async (trigger, user, role, item, config) => {
5368
+ processField = async (trigger, user, role, item, config) => {
5367
5369
  console.log("[EXULU] processing field", item.field, " in context", this.id);
5368
5370
  console.log("[EXULU] fields", this.fields.map((field2) => field2.name));
5369
5371
  const field = this.fields.find((field2) => field2.name === item.field?.replace("_s3key", ""));
@@ -5425,6 +5427,9 @@ var ExuluContext = class {
5425
5427
  results: []
5426
5428
  };
5427
5429
  };
5430
+ executeSource = async (source, inputs) => {
5431
+ return await source.execute(inputs);
5432
+ };
5428
5433
  tableExists = async () => {
5429
5434
  const { db: db3 } = await postgresClient();
5430
5435
  const tableName = getTableName(this.id);
@@ -6939,6 +6944,49 @@ var createWorkers = async (agents, queues2, config, contexts, evals, tools, trac
6939
6944
  metadata: {}
6940
6945
  };
6941
6946
  }
6947
+ if (data.type === "source") {
6948
+ console.log("[EXULU] running a source job.", logMetadata(bullmqJob.name));
6949
+ if (!data.source) {
6950
+ throw new Error(`No source id set for source job.`);
6951
+ }
6952
+ if (!data.context) {
6953
+ throw new Error(`No context id set for source job.`);
6954
+ }
6955
+ const context = contexts.find((c) => c.id === data.context);
6956
+ if (!context) {
6957
+ throw new Error(`Context ${data.context} not found in the registry.`);
6958
+ }
6959
+ const source = context.sources.find((s) => s.id === data.source);
6960
+ if (!source) {
6961
+ throw new Error(`Source ${data.source} not found in the context ${context.id}.`);
6962
+ }
6963
+ const result = await source.execute(data.inputs);
6964
+ let jobs = [];
6965
+ let items = [];
6966
+ for (const item of result) {
6967
+ const { item: createdItem, job } = await context.createItem(item, config, data.user, data.role);
6968
+ if (job) {
6969
+ jobs.push(job);
6970
+ console.log(`[EXULU] Scheduled job through source update job for item ${createdItem.id} (Job ID: ${job})`, logMetadata(bullmqJob.name, {
6971
+ item: createdItem,
6972
+ job
6973
+ }));
6974
+ }
6975
+ if (createdItem.id) {
6976
+ items.push(createdItem.id);
6977
+ console.log(`[EXULU] created item through source update job ${createdItem.id}`, logMetadata(bullmqJob.name, {
6978
+ item: createdItem
6979
+ }));
6980
+ }
6981
+ }
6982
+ return {
6983
+ result,
6984
+ metadata: {
6985
+ jobs,
6986
+ items
6987
+ }
6988
+ };
6989
+ }
6942
6990
  throw new Error(`Invalid job type: ${data.type} for job ${bullmqJob.name}.`);
6943
6991
  } catch (error) {
6944
6992
  console.error(`[EXULU] job failed.`, error instanceof Error ? error.message : String(error));
@@ -8845,6 +8893,41 @@ var ExuluApp = class {
8845
8893
  if (queues2) {
8846
8894
  filteredQueues = filteredQueues.filter((q) => queues2.includes(q.queue.name));
8847
8895
  }
8896
+ const sources = Object.values(this._contexts ?? {}).flatMap((context) => ({
8897
+ ...context.sources,
8898
+ context: context.id
8899
+ }));
8900
+ if (sources.length > 0) {
8901
+ console.log("[EXULU] Creating ContextSource schedulers for", sources.length, "sources.");
8902
+ for (const source of sources) {
8903
+ const queue = await source.config?.queue;
8904
+ if (queue) {
8905
+ if (!source.config.schedule) {
8906
+ throw new Error("Schedule is required for source when configuring a queue: " + source.name);
8907
+ }
8908
+ console.log("[EXULU] Creating ContextSource scheduler for", source.name, "in queue", queue.queue?.name);
8909
+ await queue.queue?.upsertJobScheduler(source.id, {
8910
+ pattern: source.config?.schedule
8911
+ }, {
8912
+ // default job data
8913
+ name: `${source.id}-job`,
8914
+ data: {
8915
+ source: source.id,
8916
+ context: source.context,
8917
+ type: "source"
8918
+ },
8919
+ opts: {
8920
+ backoff: {
8921
+ type: source.config.backoff?.type || "exponential",
8922
+ delay: source.config.backoff?.delay || 2e3
8923
+ },
8924
+ attempts: source.config.retries || 3,
8925
+ removeOnFail: 200
8926
+ }
8927
+ });
8928
+ }
8929
+ }
8930
+ }
8848
8931
  return await createWorkers(
8849
8932
  this._agents,
8850
8933
  filteredQueues,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@exulu/backend",
3
3
  "author": "Qventu Bv.",
4
- "version": "1.31.2",
4
+ "version": "1.32.0",
5
5
  "main": "./dist/index.js",
6
6
  "private": false,
7
7
  "publishConfig": {