@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 +3 -3
- package/dist/index.cjs +86 -3
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +86 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
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
|
-
###
|
|
4
|
+
### Features
|
|
5
5
|
|
|
6
|
-
*
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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,
|