@backstage/plugin-catalog-backend-module-incremental-ingestion 0.0.0-nightly-20221125023412
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 +21 -0
- package/README.md +341 -0
- package/alpha/package.json +6 -0
- package/dist/index.alpha.d.ts +153 -0
- package/dist/index.beta.d.ts +143 -0
- package/dist/index.cjs.js +979 -0
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +143 -0
- package/migrations/20221116073152_init.js +177 -0
- package/package.json +67 -0
|
@@ -0,0 +1,979 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
|
+
|
|
5
|
+
var backendPluginApi = require('@backstage/backend-plugin-api');
|
|
6
|
+
var pluginCatalogNode = require('@backstage/plugin-catalog-node');
|
|
7
|
+
var errors = require('@backstage/errors');
|
|
8
|
+
var luxon = require('luxon');
|
|
9
|
+
var catalogModel = require('@backstage/catalog-model');
|
|
10
|
+
var uuid = require('uuid');
|
|
11
|
+
var backendCommon = require('@backstage/backend-common');
|
|
12
|
+
var perf_hooks = require('perf_hooks');
|
|
13
|
+
var express = require('express');
|
|
14
|
+
var Router = require('express-promise-router');
|
|
15
|
+
|
|
16
|
+
function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
|
|
17
|
+
|
|
18
|
+
var express__default = /*#__PURE__*/_interopDefaultLegacy(express);
|
|
19
|
+
var Router__default = /*#__PURE__*/_interopDefaultLegacy(Router);
|
|
20
|
+
|
|
21
|
+
const INCREMENTAL_ENTITY_PROVIDER_ANNOTATION = "backstage.io/incremental-provider-name";
|
|
22
|
+
|
|
23
|
+
class IncrementalIngestionDatabaseManager {
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.client = options.client;
|
|
26
|
+
}
|
|
27
|
+
async updateIngestionRecordById(options) {
|
|
28
|
+
await this.client.transaction(async (tx) => {
|
|
29
|
+
const { ingestionId, update } = options;
|
|
30
|
+
await tx("ingestions").where("id", ingestionId).update(update);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
async updateIngestionRecordByProvider(provider, update) {
|
|
34
|
+
await this.client.transaction(async (tx) => {
|
|
35
|
+
await tx("ingestions").where("provider_name", provider).andWhere("completion_ticket", "open").update(update);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
async insertIngestionRecord(record) {
|
|
39
|
+
await this.client.transaction(async (tx) => {
|
|
40
|
+
await tx("ingestions").insert(record);
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
async deleteMarkEntities(tx, ids) {
|
|
44
|
+
const chunks = [];
|
|
45
|
+
for (let i = 0; i < ids.length; i += 100) {
|
|
46
|
+
const chunk = ids.slice(i, i + 100);
|
|
47
|
+
chunks.push(chunk);
|
|
48
|
+
}
|
|
49
|
+
let deleted = 0;
|
|
50
|
+
for (const chunk of chunks) {
|
|
51
|
+
const chunkDeleted = await tx("ingestion_mark_entities").delete().whereIn(
|
|
52
|
+
"id",
|
|
53
|
+
chunk.map((entry) => entry.id)
|
|
54
|
+
);
|
|
55
|
+
deleted += chunkDeleted;
|
|
56
|
+
}
|
|
57
|
+
return deleted;
|
|
58
|
+
}
|
|
59
|
+
async getCurrentIngestionRecord(provider) {
|
|
60
|
+
return await this.client.transaction(async (tx) => {
|
|
61
|
+
const record = await tx("ingestions").where("provider_name", provider).andWhere("completion_ticket", "open").first();
|
|
62
|
+
return record;
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
async clearFinishedIngestions(provider) {
|
|
66
|
+
return await this.client.transaction(async (tx) => {
|
|
67
|
+
const markEntitiesDeleted = await tx("ingestion_mark_entities").delete().whereIn(
|
|
68
|
+
"ingestion_mark_id",
|
|
69
|
+
tx("ingestion_marks").select("id").whereIn(
|
|
70
|
+
"ingestion_id",
|
|
71
|
+
tx("ingestions").select("id").where("provider_name", provider).andWhereNot("completion_ticket", "open")
|
|
72
|
+
)
|
|
73
|
+
);
|
|
74
|
+
const marksDeleted = await tx("ingestion_marks").delete().whereIn(
|
|
75
|
+
"ingestion_id",
|
|
76
|
+
tx("ingestions").select("id").where("provider_name", provider).andWhereNot("completion_ticket", "open")
|
|
77
|
+
);
|
|
78
|
+
const ingestionsDeleted = await tx("ingestions").delete().where("provider_name", provider).andWhereNot("completion_ticket", "open");
|
|
79
|
+
return {
|
|
80
|
+
deletions: {
|
|
81
|
+
markEntitiesDeleted,
|
|
82
|
+
marksDeleted,
|
|
83
|
+
ingestionsDeleted
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
async clearDuplicateIngestions(ingestionId, provider) {
|
|
89
|
+
await this.client.transaction(async (tx) => {
|
|
90
|
+
const invalid = await tx("ingestions").where("provider_name", provider).andWhere("rest_completed_at", null).andWhereNot("id", ingestionId);
|
|
91
|
+
if (invalid.length > 0) {
|
|
92
|
+
await tx("ingestions").delete().whereIn("id", invalid);
|
|
93
|
+
await tx("ingestion_mark_entities").delete().whereIn(
|
|
94
|
+
"ingestion_mark_id",
|
|
95
|
+
tx("ingestion_marks").select("id").whereIn("ingestion_id", invalid)
|
|
96
|
+
);
|
|
97
|
+
await tx("ingestion_marks").delete().whereIn("ingestion_id", invalid);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async purgeAndResetProvider(provider) {
|
|
102
|
+
return await this.client.transaction(async (tx) => {
|
|
103
|
+
const ingestionIDs = await tx("ingestions").select("id").where("provider_name", provider);
|
|
104
|
+
const markIDs = ingestionIDs.length > 0 ? await tx("ingestion_marks").select("id").whereIn(
|
|
105
|
+
"ingestion_id",
|
|
106
|
+
ingestionIDs.map((entry) => entry.id)
|
|
107
|
+
) : [];
|
|
108
|
+
const markEntityIDs = markIDs.length > 0 ? await tx("ingestion_mark_entities").select("id").whereIn(
|
|
109
|
+
"ingestion_mark_id",
|
|
110
|
+
markIDs.map((entry) => entry.id)
|
|
111
|
+
) : [];
|
|
112
|
+
const markEntitiesDeleted = await this.deleteMarkEntities(
|
|
113
|
+
tx,
|
|
114
|
+
markEntityIDs
|
|
115
|
+
);
|
|
116
|
+
const marksDeleted = markIDs.length > 0 ? await tx("ingestion_marks").delete().whereIn(
|
|
117
|
+
"ingestion_id",
|
|
118
|
+
ingestionIDs.map((entry) => entry.id)
|
|
119
|
+
) : 0;
|
|
120
|
+
const ingestionsDeleted = await tx("ingestions").delete().where("provider_name", provider);
|
|
121
|
+
const next_action_at = new Date();
|
|
122
|
+
next_action_at.setTime(next_action_at.getTime() + 24 * 60 * 60 * 1e3);
|
|
123
|
+
await this.insertIngestionRecord({
|
|
124
|
+
id: uuid.v4(),
|
|
125
|
+
next_action: "rest",
|
|
126
|
+
provider_name: provider,
|
|
127
|
+
next_action_at,
|
|
128
|
+
ingestion_completed_at: new Date(),
|
|
129
|
+
status: "resting",
|
|
130
|
+
completion_ticket: "open"
|
|
131
|
+
});
|
|
132
|
+
return { provider, ingestionsDeleted, marksDeleted, markEntitiesDeleted };
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
async createProviderIngestionRecord(provider) {
|
|
136
|
+
const ingestionId = uuid.v4();
|
|
137
|
+
const nextAction = "ingest";
|
|
138
|
+
try {
|
|
139
|
+
await this.insertIngestionRecord({
|
|
140
|
+
id: ingestionId,
|
|
141
|
+
next_action: nextAction,
|
|
142
|
+
provider_name: provider,
|
|
143
|
+
status: "bursting",
|
|
144
|
+
completion_ticket: "open"
|
|
145
|
+
});
|
|
146
|
+
return { ingestionId, nextAction, attempts: 0, nextActionAt: Date.now() };
|
|
147
|
+
} catch (_e) {
|
|
148
|
+
return void 0;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
async computeRemoved(provider, ingestionId) {
|
|
152
|
+
return await this.client.transaction(async (tx) => {
|
|
153
|
+
const removed = await tx(
|
|
154
|
+
"final_entities"
|
|
155
|
+
).select(
|
|
156
|
+
tx.ref("final_entity").as("entity"),
|
|
157
|
+
tx.ref("refresh_state.entity_ref").as("ref")
|
|
158
|
+
).join(
|
|
159
|
+
"refresh_state",
|
|
160
|
+
"refresh_state.entity_id",
|
|
161
|
+
"final_entities.entity_id"
|
|
162
|
+
).join("search", "search.entity_id", "final_entities.entity_id").whereNotIn(
|
|
163
|
+
"entity_ref",
|
|
164
|
+
tx("ingestion_marks").join(
|
|
165
|
+
"ingestion_mark_entities",
|
|
166
|
+
"ingestion_marks.id",
|
|
167
|
+
"ingestion_mark_entities.ingestion_mark_id"
|
|
168
|
+
).select("ingestion_mark_entities.ref").where("ingestion_marks.ingestion_id", ingestionId)
|
|
169
|
+
).andWhere(
|
|
170
|
+
"search.key",
|
|
171
|
+
`metadata.annotations.${INCREMENTAL_ENTITY_PROVIDER_ANNOTATION}`
|
|
172
|
+
).andWhere("search.value", provider);
|
|
173
|
+
return removed.map((entity) => {
|
|
174
|
+
return { entity: JSON.parse(entity.entity) };
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
async healthcheck() {
|
|
179
|
+
return await this.client.transaction(async (tx) => {
|
|
180
|
+
const records = await tx(
|
|
181
|
+
"ingestions"
|
|
182
|
+
).distinct("id", "provider_name").where("rest_completed_at", null);
|
|
183
|
+
return records;
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
async triggerNextProviderAction(provider) {
|
|
187
|
+
await this.updateIngestionRecordByProvider(provider, {
|
|
188
|
+
next_action_at: new Date()
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async cleanupProviders() {
|
|
192
|
+
const providers = await this.listProviders();
|
|
193
|
+
const ingestionsDeleted = await this.purgeTable("ingestions");
|
|
194
|
+
const next_action_at = new Date();
|
|
195
|
+
next_action_at.setTime(next_action_at.getTime() + 24 * 60 * 60 * 1e3);
|
|
196
|
+
for (const provider of providers) {
|
|
197
|
+
await this.insertIngestionRecord({
|
|
198
|
+
id: uuid.v4(),
|
|
199
|
+
next_action: "rest",
|
|
200
|
+
provider_name: provider,
|
|
201
|
+
next_action_at,
|
|
202
|
+
ingestion_completed_at: new Date(),
|
|
203
|
+
status: "resting",
|
|
204
|
+
completion_ticket: "open"
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
const ingestionMarksDeleted = await this.purgeTable("ingestion_marks");
|
|
208
|
+
const markEntitiesDeleted = await this.purgeTable(
|
|
209
|
+
"ingestion_mark_entities"
|
|
210
|
+
);
|
|
211
|
+
return { ingestionsDeleted, ingestionMarksDeleted, markEntitiesDeleted };
|
|
212
|
+
}
|
|
213
|
+
async setProviderIngesting(ingestionId) {
|
|
214
|
+
await this.updateIngestionRecordById({
|
|
215
|
+
ingestionId,
|
|
216
|
+
update: { next_action: "ingest" }
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
async setProviderBursting(ingestionId) {
|
|
220
|
+
await this.updateIngestionRecordById({
|
|
221
|
+
ingestionId,
|
|
222
|
+
update: { status: "bursting" }
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
async setProviderComplete(ingestionId) {
|
|
226
|
+
await this.updateIngestionRecordById({
|
|
227
|
+
ingestionId,
|
|
228
|
+
update: {
|
|
229
|
+
next_action: "nothing (done)",
|
|
230
|
+
rest_completed_at: new Date(),
|
|
231
|
+
status: "complete",
|
|
232
|
+
completion_ticket: uuid.v4()
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
async setProviderResting(ingestionId, restLength) {
|
|
237
|
+
await this.updateIngestionRecordById({
|
|
238
|
+
ingestionId,
|
|
239
|
+
update: {
|
|
240
|
+
next_action: "rest",
|
|
241
|
+
next_action_at: new Date(Date.now() + restLength.as("milliseconds")),
|
|
242
|
+
ingestion_completed_at: new Date(),
|
|
243
|
+
status: "resting"
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
async setProviderInterstitial(ingestionId) {
|
|
248
|
+
await this.updateIngestionRecordById({
|
|
249
|
+
ingestionId,
|
|
250
|
+
update: { attempts: 0, status: "interstitial" }
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
async setProviderCanceling(ingestionId, message) {
|
|
254
|
+
const update = {
|
|
255
|
+
next_action: "cancel",
|
|
256
|
+
last_error: message ? message : void 0,
|
|
257
|
+
next_action_at: new Date(),
|
|
258
|
+
status: "canceling"
|
|
259
|
+
};
|
|
260
|
+
await this.updateIngestionRecordById({ ingestionId, update });
|
|
261
|
+
}
|
|
262
|
+
async setProviderCanceled(ingestionId) {
|
|
263
|
+
await this.updateIngestionRecordById({
|
|
264
|
+
ingestionId,
|
|
265
|
+
update: {
|
|
266
|
+
next_action: "nothing (canceled)",
|
|
267
|
+
rest_completed_at: new Date(),
|
|
268
|
+
status: "complete",
|
|
269
|
+
completion_ticket: uuid.v4()
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
async setProviderBackoff(ingestionId, attempts, error, backoffLength) {
|
|
274
|
+
await this.updateIngestionRecordById({
|
|
275
|
+
ingestionId,
|
|
276
|
+
update: {
|
|
277
|
+
next_action: "backoff",
|
|
278
|
+
attempts: attempts + 1,
|
|
279
|
+
last_error: String(error),
|
|
280
|
+
next_action_at: new Date(Date.now() + backoffLength),
|
|
281
|
+
status: "backing off"
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
async getLastMark(ingestionId) {
|
|
286
|
+
return await this.client.transaction(async (tx) => {
|
|
287
|
+
const mark = await tx("ingestion_marks").where("ingestion_id", ingestionId).orderBy("sequence", "desc").first();
|
|
288
|
+
return mark;
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
async getAllMarks(ingestionId) {
|
|
292
|
+
return await this.client.transaction(async (tx) => {
|
|
293
|
+
const marks = await tx("ingestion_marks").where("ingestion_id", ingestionId).orderBy("sequence", "desc");
|
|
294
|
+
return marks;
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
async createMark(options) {
|
|
298
|
+
const { record } = options;
|
|
299
|
+
await this.client.transaction(async (tx) => {
|
|
300
|
+
await tx("ingestion_marks").insert(record);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
async createMarkEntities(markId, entities) {
|
|
304
|
+
await this.client.transaction(async (tx) => {
|
|
305
|
+
await tx("ingestion_mark_entities").insert(
|
|
306
|
+
entities.map((entity) => ({
|
|
307
|
+
id: uuid.v4(),
|
|
308
|
+
ingestion_mark_id: markId,
|
|
309
|
+
ref: catalogModel.stringifyEntityRef(entity.entity)
|
|
310
|
+
}))
|
|
311
|
+
);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
async purgeTable(table) {
|
|
315
|
+
return await this.client.transaction(async (tx) => {
|
|
316
|
+
return await tx(table).delete();
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
async listProviders() {
|
|
320
|
+
return await this.client.transaction(async (tx) => {
|
|
321
|
+
const providers = await tx(
|
|
322
|
+
"ingestions"
|
|
323
|
+
).distinct("provider_name");
|
|
324
|
+
return providers.map((entry) => entry.provider_name);
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
async updateByName(provider, update) {
|
|
328
|
+
await this.updateIngestionRecordByProvider(provider, update);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const DB_MIGRATIONS_TABLE = "incremental_ingestion__knex_migrations";
|
|
333
|
+
|
|
334
|
+
async function applyDatabaseMigrations(knex) {
|
|
335
|
+
const migrationsDir = backendCommon.resolvePackagePath(
|
|
336
|
+
"@backstage/plugin-catalog-backend-module-incremental-ingestion",
|
|
337
|
+
"migrations"
|
|
338
|
+
);
|
|
339
|
+
await knex.migrate.latest({
|
|
340
|
+
directory: migrationsDir,
|
|
341
|
+
tableName: DB_MIGRATIONS_TABLE
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
class IncrementalIngestionEngine {
|
|
346
|
+
constructor(options) {
|
|
347
|
+
this.options = options;
|
|
348
|
+
var _a;
|
|
349
|
+
this.manager = options.manager;
|
|
350
|
+
this.restLength = luxon.Duration.fromObject(options.restLength);
|
|
351
|
+
this.backoff = (_a = options.backoff) != null ? _a : [
|
|
352
|
+
{ minutes: 1 },
|
|
353
|
+
{ minutes: 5 },
|
|
354
|
+
{ minutes: 30 },
|
|
355
|
+
{ hours: 3 }
|
|
356
|
+
];
|
|
357
|
+
}
|
|
358
|
+
async taskFn(signal) {
|
|
359
|
+
try {
|
|
360
|
+
this.options.logger.debug("Begin tick");
|
|
361
|
+
await this.handleNextAction(signal);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
this.options.logger.error(`${error}`);
|
|
364
|
+
throw error;
|
|
365
|
+
} finally {
|
|
366
|
+
this.options.logger.debug("End tick");
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
async handleNextAction(signal) {
|
|
370
|
+
await this.options.ready;
|
|
371
|
+
const result = await this.getCurrentAction();
|
|
372
|
+
if (result) {
|
|
373
|
+
const { ingestionId, nextActionAt, nextAction, attempts } = result;
|
|
374
|
+
switch (nextAction) {
|
|
375
|
+
case "rest":
|
|
376
|
+
if (Date.now() > nextActionAt) {
|
|
377
|
+
await this.manager.clearFinishedIngestions(
|
|
378
|
+
this.options.provider.getProviderName()
|
|
379
|
+
);
|
|
380
|
+
this.options.logger.info(
|
|
381
|
+
`incremental-engine: Ingestion ${ingestionId} rest period complete. Ingestion will start again`
|
|
382
|
+
);
|
|
383
|
+
await this.manager.setProviderComplete(ingestionId);
|
|
384
|
+
} else {
|
|
385
|
+
this.options.logger.info(
|
|
386
|
+
`incremental-engine: Ingestion '${ingestionId}' rest period continuing`
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
break;
|
|
390
|
+
case "ingest":
|
|
391
|
+
try {
|
|
392
|
+
await this.manager.setProviderBursting(ingestionId);
|
|
393
|
+
const done = await this.ingestOneBurst(ingestionId, signal);
|
|
394
|
+
if (done) {
|
|
395
|
+
this.options.logger.info(
|
|
396
|
+
`incremental-engine: Ingestion '${ingestionId}' complete, transitioning to rest period of ${this.restLength.toHuman()}`
|
|
397
|
+
);
|
|
398
|
+
await this.manager.setProviderResting(
|
|
399
|
+
ingestionId,
|
|
400
|
+
this.restLength
|
|
401
|
+
);
|
|
402
|
+
} else {
|
|
403
|
+
await this.manager.setProviderInterstitial(ingestionId);
|
|
404
|
+
this.options.logger.info(
|
|
405
|
+
`incremental-engine: Ingestion '${ingestionId}' continuing`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (error.message && error.message === "CANCEL") {
|
|
410
|
+
this.options.logger.info(
|
|
411
|
+
`incremental-engine: Ingestion '${ingestionId}' canceled`
|
|
412
|
+
);
|
|
413
|
+
await this.manager.setProviderCanceling(
|
|
414
|
+
ingestionId,
|
|
415
|
+
error.message
|
|
416
|
+
);
|
|
417
|
+
} else {
|
|
418
|
+
const currentBackoff = luxon.Duration.fromObject(
|
|
419
|
+
this.backoff[Math.min(this.backoff.length - 1, attempts)]
|
|
420
|
+
);
|
|
421
|
+
const backoffLength = currentBackoff.as("milliseconds");
|
|
422
|
+
this.options.logger.error(error);
|
|
423
|
+
const truncatedError = errors.stringifyError(error).substring(0, 700);
|
|
424
|
+
this.options.logger.error(
|
|
425
|
+
`incremental-engine: Ingestion '${ingestionId}' threw an error during ingestion burst. Ingestion will backoff for ${currentBackoff.toHuman()} (${truncatedError})`
|
|
426
|
+
);
|
|
427
|
+
await this.manager.setProviderBackoff(
|
|
428
|
+
ingestionId,
|
|
429
|
+
attempts,
|
|
430
|
+
error,
|
|
431
|
+
backoffLength
|
|
432
|
+
);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
break;
|
|
436
|
+
case "backoff":
|
|
437
|
+
if (Date.now() > nextActionAt) {
|
|
438
|
+
this.options.logger.info(
|
|
439
|
+
`incremental-engine: Ingestion '${ingestionId}' backoff complete, will attempt to resume`
|
|
440
|
+
);
|
|
441
|
+
await this.manager.setProviderIngesting(ingestionId);
|
|
442
|
+
} else {
|
|
443
|
+
this.options.logger.info(
|
|
444
|
+
`incremental-engine: Ingestion '${ingestionId}' backoff continuing`
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
break;
|
|
448
|
+
case "cancel":
|
|
449
|
+
this.options.logger.info(
|
|
450
|
+
`incremental-engine: Ingestion '${ingestionId}' canceling, will restart`
|
|
451
|
+
);
|
|
452
|
+
await this.manager.setProviderCanceled(ingestionId);
|
|
453
|
+
break;
|
|
454
|
+
default:
|
|
455
|
+
this.options.logger.error(
|
|
456
|
+
`incremental-engine: Ingestion '${ingestionId}' received unknown action '${nextAction}'`
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
} else {
|
|
460
|
+
this.options.logger.error(
|
|
461
|
+
`incremental-engine: Engine tried to create duplicate ingestion record for provider '${this.options.provider.getProviderName()}'.`
|
|
462
|
+
);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
async getCurrentAction() {
|
|
466
|
+
const providerName = this.options.provider.getProviderName();
|
|
467
|
+
const record = await this.manager.getCurrentIngestionRecord(providerName);
|
|
468
|
+
if (record) {
|
|
469
|
+
this.options.logger.info(
|
|
470
|
+
`incremental-engine: Ingestion record found: '${record.id}'`
|
|
471
|
+
);
|
|
472
|
+
return {
|
|
473
|
+
ingestionId: record.id,
|
|
474
|
+
nextAction: record.next_action,
|
|
475
|
+
attempts: record.attempts,
|
|
476
|
+
nextActionAt: record.next_action_at.valueOf()
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
const result = await this.manager.createProviderIngestionRecord(
|
|
480
|
+
providerName
|
|
481
|
+
);
|
|
482
|
+
if (result) {
|
|
483
|
+
this.options.logger.info(
|
|
484
|
+
`incremental-engine: Ingestion record created: '${result.ingestionId}'`
|
|
485
|
+
);
|
|
486
|
+
}
|
|
487
|
+
return result;
|
|
488
|
+
}
|
|
489
|
+
async ingestOneBurst(id, signal) {
|
|
490
|
+
const lastMark = await this.manager.getLastMark(id);
|
|
491
|
+
const cursor = lastMark ? lastMark.cursor : void 0;
|
|
492
|
+
let sequence = lastMark ? lastMark.sequence + 1 : 0;
|
|
493
|
+
const start = perf_hooks.performance.now();
|
|
494
|
+
let count = 0;
|
|
495
|
+
let done = false;
|
|
496
|
+
this.options.logger.info(
|
|
497
|
+
`incremental-engine: Ingestion '${id}' burst initiated`
|
|
498
|
+
);
|
|
499
|
+
await this.options.provider.around(async (context) => {
|
|
500
|
+
let next = await this.options.provider.next(context, cursor);
|
|
501
|
+
count++;
|
|
502
|
+
for (; ; ) {
|
|
503
|
+
done = next.done;
|
|
504
|
+
await this.mark({
|
|
505
|
+
id,
|
|
506
|
+
sequence,
|
|
507
|
+
entities: next == null ? void 0 : next.entities,
|
|
508
|
+
done: next.done,
|
|
509
|
+
cursor: next == null ? void 0 : next.cursor
|
|
510
|
+
});
|
|
511
|
+
if (signal.aborted || next.done) {
|
|
512
|
+
break;
|
|
513
|
+
} else {
|
|
514
|
+
next = await this.options.provider.next(context, next.cursor);
|
|
515
|
+
count++;
|
|
516
|
+
sequence++;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
});
|
|
520
|
+
this.options.logger.info(
|
|
521
|
+
`incremental-engine: Ingestion '${id}' burst complete. (${count} batches in ${Math.round(
|
|
522
|
+
perf_hooks.performance.now() - start
|
|
523
|
+
)}ms).`
|
|
524
|
+
);
|
|
525
|
+
return done;
|
|
526
|
+
}
|
|
527
|
+
async mark(options) {
|
|
528
|
+
var _a;
|
|
529
|
+
const { id, sequence, entities, done, cursor } = options;
|
|
530
|
+
this.options.logger.debug(
|
|
531
|
+
`incremental-engine: Ingestion '${id}': MARK ${entities ? entities.length : 0} entities, cursor: ${cursor ? JSON.stringify(cursor) : "none"}, done: ${done}`
|
|
532
|
+
);
|
|
533
|
+
const markId = uuid.v4();
|
|
534
|
+
await this.manager.createMark({
|
|
535
|
+
record: {
|
|
536
|
+
id: markId,
|
|
537
|
+
ingestion_id: id,
|
|
538
|
+
cursor,
|
|
539
|
+
sequence
|
|
540
|
+
}
|
|
541
|
+
});
|
|
542
|
+
if (entities && entities.length > 0) {
|
|
543
|
+
await this.manager.createMarkEntities(markId, entities);
|
|
544
|
+
}
|
|
545
|
+
const added = (_a = entities == null ? void 0 : entities.map((deferred) => ({
|
|
546
|
+
...deferred,
|
|
547
|
+
entity: {
|
|
548
|
+
...deferred.entity,
|
|
549
|
+
metadata: {
|
|
550
|
+
...deferred.entity.metadata,
|
|
551
|
+
annotations: {
|
|
552
|
+
...deferred.entity.metadata.annotations,
|
|
553
|
+
[INCREMENTAL_ENTITY_PROVIDER_ANNOTATION]: this.options.provider.getProviderName()
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}))) != null ? _a : [];
|
|
558
|
+
const removed = [];
|
|
559
|
+
if (done) {
|
|
560
|
+
removed.push(
|
|
561
|
+
...await this.manager.computeRemoved(
|
|
562
|
+
this.options.provider.getProviderName(),
|
|
563
|
+
id
|
|
564
|
+
)
|
|
565
|
+
);
|
|
566
|
+
}
|
|
567
|
+
await this.options.connection.applyMutation({
|
|
568
|
+
type: "delta",
|
|
569
|
+
added,
|
|
570
|
+
removed
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const PROVIDER_CLEANUP = "/incremental/cleanup";
|
|
576
|
+
const PROVIDER_HEALTH = "/incremental/health";
|
|
577
|
+
const PROVIDER_BASE_PATH = "/incremental/providers/:provider";
|
|
578
|
+
|
|
579
|
+
const createIncrementalProviderRouter = async (manager, logger) => {
|
|
580
|
+
const router = Router__default["default"]();
|
|
581
|
+
router.use(express__default["default"].json());
|
|
582
|
+
router.get(PROVIDER_HEALTH, async (_, res) => {
|
|
583
|
+
const records = await manager.healthcheck();
|
|
584
|
+
const providers = records.map((record) => record.provider_name);
|
|
585
|
+
const duplicates = [
|
|
586
|
+
...new Set(providers.filter((e, i, a) => a.indexOf(e) !== i))
|
|
587
|
+
];
|
|
588
|
+
if (duplicates.length > 0) {
|
|
589
|
+
res.json({ healthy: false, duplicateIngestions: duplicates });
|
|
590
|
+
} else {
|
|
591
|
+
res.json({ healthy: true });
|
|
592
|
+
}
|
|
593
|
+
});
|
|
594
|
+
router.post(PROVIDER_CLEANUP, async (_, res) => {
|
|
595
|
+
const result = await manager.cleanupProviders();
|
|
596
|
+
res.json(result);
|
|
597
|
+
});
|
|
598
|
+
router.get(PROVIDER_BASE_PATH, async (req, res) => {
|
|
599
|
+
const { provider } = req.params;
|
|
600
|
+
const record = await manager.getCurrentIngestionRecord(provider);
|
|
601
|
+
if (record) {
|
|
602
|
+
res.json({
|
|
603
|
+
success: true,
|
|
604
|
+
status: {
|
|
605
|
+
current_action: record.status,
|
|
606
|
+
next_action_at: new Date(record.next_action_at)
|
|
607
|
+
},
|
|
608
|
+
last_error: record.last_error
|
|
609
|
+
});
|
|
610
|
+
} else {
|
|
611
|
+
const providers = await manager.listProviders();
|
|
612
|
+
if (providers.includes(provider)) {
|
|
613
|
+
res.json({
|
|
614
|
+
success: true,
|
|
615
|
+
status: {
|
|
616
|
+
current_action: "rest complete, waiting to start"
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
} else {
|
|
620
|
+
logger.error(
|
|
621
|
+
`${provider} - No ingestion record found in the database!`
|
|
622
|
+
);
|
|
623
|
+
res.status(404).json({
|
|
624
|
+
success: false,
|
|
625
|
+
status: {},
|
|
626
|
+
last_error: `Provider '${provider}' not found`
|
|
627
|
+
});
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
});
|
|
631
|
+
router.post(`${PROVIDER_BASE_PATH}/trigger`, async (req, res) => {
|
|
632
|
+
const { provider } = req.params;
|
|
633
|
+
const record = await manager.getCurrentIngestionRecord(provider);
|
|
634
|
+
if (record) {
|
|
635
|
+
await manager.triggerNextProviderAction(provider);
|
|
636
|
+
res.json({
|
|
637
|
+
success: true,
|
|
638
|
+
message: `${provider}: Next action triggered.`
|
|
639
|
+
});
|
|
640
|
+
} else {
|
|
641
|
+
const providers = await manager.listProviders();
|
|
642
|
+
if (providers.includes(provider)) {
|
|
643
|
+
logger.debug(`${provider} - Ingestion record found`);
|
|
644
|
+
res.json({
|
|
645
|
+
success: true,
|
|
646
|
+
message: "Unable to trigger next action (provider is restarting)"
|
|
647
|
+
});
|
|
648
|
+
} else {
|
|
649
|
+
res.status(404).json({
|
|
650
|
+
success: false,
|
|
651
|
+
message: `Provider '${provider}' not found`
|
|
652
|
+
});
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
});
|
|
656
|
+
router.post(`${PROVIDER_BASE_PATH}/start`, async (req, res) => {
|
|
657
|
+
const { provider } = req.params;
|
|
658
|
+
const record = await manager.getCurrentIngestionRecord(provider);
|
|
659
|
+
if (record) {
|
|
660
|
+
const ingestionId = record.id;
|
|
661
|
+
if (record.status === "resting") {
|
|
662
|
+
await manager.setProviderComplete(ingestionId);
|
|
663
|
+
} else {
|
|
664
|
+
await manager.setProviderCanceling(ingestionId);
|
|
665
|
+
}
|
|
666
|
+
res.json({
|
|
667
|
+
success: true,
|
|
668
|
+
message: `${provider}: Next cycle triggered.`
|
|
669
|
+
});
|
|
670
|
+
} else {
|
|
671
|
+
const providers = await manager.listProviders();
|
|
672
|
+
if (providers.includes(provider)) {
|
|
673
|
+
logger.debug(`${provider} - Ingestion record found`);
|
|
674
|
+
res.json({
|
|
675
|
+
success: true,
|
|
676
|
+
message: "Provider is already restarting"
|
|
677
|
+
});
|
|
678
|
+
} else {
|
|
679
|
+
res.status(404).json({
|
|
680
|
+
success: false,
|
|
681
|
+
message: `Provider '${provider}' not found`
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
router.post(`${PROVIDER_BASE_PATH}/cancel`, async (req, res) => {
|
|
687
|
+
const { provider } = req.params;
|
|
688
|
+
const record = await manager.getCurrentIngestionRecord(provider);
|
|
689
|
+
if (record) {
|
|
690
|
+
const next_action_at = new Date();
|
|
691
|
+
next_action_at.setTime(next_action_at.getTime() + 24 * 60 * 60 * 1e3);
|
|
692
|
+
await manager.updateByName(provider, {
|
|
693
|
+
next_action: "nothing (done)",
|
|
694
|
+
ingestion_completed_at: new Date(),
|
|
695
|
+
next_action_at,
|
|
696
|
+
status: "resting"
|
|
697
|
+
});
|
|
698
|
+
res.json({
|
|
699
|
+
success: true,
|
|
700
|
+
message: `${provider}: Current ingestion canceled.`
|
|
701
|
+
});
|
|
702
|
+
} else {
|
|
703
|
+
const providers = await manager.listProviders();
|
|
704
|
+
if (providers.includes(provider)) {
|
|
705
|
+
logger.debug(`${provider} - Ingestion record found`);
|
|
706
|
+
res.json({
|
|
707
|
+
success: true,
|
|
708
|
+
message: "Provider is currently restarting, please wait."
|
|
709
|
+
});
|
|
710
|
+
} else {
|
|
711
|
+
res.status(404).json({
|
|
712
|
+
success: false,
|
|
713
|
+
message: `Provider '${provider}' not found`
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
});
|
|
718
|
+
router.delete(PROVIDER_BASE_PATH, async (req, res) => {
|
|
719
|
+
const { provider } = req.params;
|
|
720
|
+
const result = await manager.purgeAndResetProvider(provider);
|
|
721
|
+
res.json(result);
|
|
722
|
+
});
|
|
723
|
+
router.get(`${PROVIDER_BASE_PATH}/marks`, async (req, res) => {
|
|
724
|
+
const { provider } = req.params;
|
|
725
|
+
const record = await manager.getCurrentIngestionRecord(provider);
|
|
726
|
+
if (record) {
|
|
727
|
+
const id = record.id;
|
|
728
|
+
const records = await manager.getAllMarks(id);
|
|
729
|
+
res.json({ success: true, records });
|
|
730
|
+
} else {
|
|
731
|
+
const providers = await manager.listProviders();
|
|
732
|
+
if (providers.includes(provider)) {
|
|
733
|
+
logger.debug(`${provider} - Ingestion record found`);
|
|
734
|
+
res.json({
|
|
735
|
+
success: true,
|
|
736
|
+
message: "No records yet (provider is restarting)"
|
|
737
|
+
});
|
|
738
|
+
} else {
|
|
739
|
+
logger.error(
|
|
740
|
+
`${provider} - No ingestion record found in the database!`
|
|
741
|
+
);
|
|
742
|
+
res.status(404).json({
|
|
743
|
+
success: false,
|
|
744
|
+
status: {},
|
|
745
|
+
last_error: `Provider '${provider}' not found`
|
|
746
|
+
});
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
});
|
|
750
|
+
router.delete(`${PROVIDER_BASE_PATH}/marks`, async (req, res) => {
|
|
751
|
+
const { provider } = req.params;
|
|
752
|
+
const deletions = await manager.clearFinishedIngestions(provider);
|
|
753
|
+
res.json({
|
|
754
|
+
success: true,
|
|
755
|
+
message: `Expired marks for provider '${provider}' removed.`,
|
|
756
|
+
deletions
|
|
757
|
+
});
|
|
758
|
+
});
|
|
759
|
+
router.use(backendCommon.errorHandler());
|
|
760
|
+
return router;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
var __accessCheck = (obj, member, msg) => {
|
|
764
|
+
if (!member.has(obj))
|
|
765
|
+
throw TypeError("Cannot " + msg);
|
|
766
|
+
};
|
|
767
|
+
var __privateGet = (obj, member, getter) => {
|
|
768
|
+
__accessCheck(obj, member, "read from private field");
|
|
769
|
+
return getter ? getter.call(obj) : member.get(obj);
|
|
770
|
+
};
|
|
771
|
+
var __privateAdd = (obj, member, value) => {
|
|
772
|
+
if (member.has(obj))
|
|
773
|
+
throw TypeError("Cannot add the same private member more than once");
|
|
774
|
+
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
775
|
+
};
|
|
776
|
+
var __privateSet = (obj, member, value, setter) => {
|
|
777
|
+
__accessCheck(obj, member, "write to private field");
|
|
778
|
+
setter ? setter.call(obj, value) : member.set(obj, value);
|
|
779
|
+
return value;
|
|
780
|
+
};
|
|
781
|
+
var _resolve, _reject, _a;
|
|
782
|
+
class Deferred {
|
|
783
|
+
constructor() {
|
|
784
|
+
__privateAdd(this, _resolve, void 0);
|
|
785
|
+
__privateAdd(this, _reject, void 0);
|
|
786
|
+
this[_a] = "Deferred";
|
|
787
|
+
const promise = new Promise((resolve, reject) => {
|
|
788
|
+
__privateSet(this, _resolve, resolve);
|
|
789
|
+
__privateSet(this, _reject, reject);
|
|
790
|
+
});
|
|
791
|
+
this.then = promise.then.bind(promise);
|
|
792
|
+
this.catch = promise.catch.bind(promise);
|
|
793
|
+
this.finally = promise.finally.bind(promise);
|
|
794
|
+
}
|
|
795
|
+
get resolve() {
|
|
796
|
+
return __privateGet(this, _resolve);
|
|
797
|
+
}
|
|
798
|
+
get reject() {
|
|
799
|
+
return __privateGet(this, _reject);
|
|
800
|
+
}
|
|
801
|
+
}
|
|
802
|
+
_a = Symbol.toStringTag;
|
|
803
|
+
_resolve = new WeakMap();
|
|
804
|
+
_reject = new WeakMap();
|
|
805
|
+
|
|
806
|
+
class WrapperProviders {
|
|
807
|
+
constructor(options) {
|
|
808
|
+
this.options = options;
|
|
809
|
+
this.numberOfProvidersToConnect = 0;
|
|
810
|
+
this.readySignal = new Deferred();
|
|
811
|
+
}
|
|
812
|
+
wrap(provider, options) {
|
|
813
|
+
this.numberOfProvidersToConnect += 1;
|
|
814
|
+
return {
|
|
815
|
+
getProviderName: () => provider.getProviderName(),
|
|
816
|
+
connect: async (connection) => {
|
|
817
|
+
await this.startProvider(provider, options, connection);
|
|
818
|
+
this.numberOfProvidersToConnect -= 1;
|
|
819
|
+
if (this.numberOfProvidersToConnect === 0) {
|
|
820
|
+
this.readySignal.resolve();
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
async adminRouter() {
|
|
826
|
+
return createIncrementalProviderRouter(
|
|
827
|
+
new IncrementalIngestionDatabaseManager({ client: this.options.client }),
|
|
828
|
+
backendPluginApi.loggerToWinstonLogger(this.options.logger)
|
|
829
|
+
);
|
|
830
|
+
}
|
|
831
|
+
async startProvider(provider, providerOptions, connection) {
|
|
832
|
+
const logger = backendPluginApi.loggerToWinstonLogger(
|
|
833
|
+
this.options.logger.child({
|
|
834
|
+
entityProvider: provider.getProviderName()
|
|
835
|
+
})
|
|
836
|
+
);
|
|
837
|
+
try {
|
|
838
|
+
if (!this.migrate) {
|
|
839
|
+
this.migrate = Promise.resolve().then(async () => {
|
|
840
|
+
var _a;
|
|
841
|
+
const apply = (_a = this.options.applyDatabaseMigrations) != null ? _a : applyDatabaseMigrations;
|
|
842
|
+
await apply(this.options.client);
|
|
843
|
+
});
|
|
844
|
+
}
|
|
845
|
+
await this.migrate;
|
|
846
|
+
const { burstInterval, burstLength, restLength } = providerOptions;
|
|
847
|
+
logger.info(`Connecting`);
|
|
848
|
+
const manager = new IncrementalIngestionDatabaseManager({
|
|
849
|
+
client: this.options.client
|
|
850
|
+
});
|
|
851
|
+
const engine = new IncrementalIngestionEngine({
|
|
852
|
+
...providerOptions,
|
|
853
|
+
ready: this.readySignal,
|
|
854
|
+
manager,
|
|
855
|
+
logger,
|
|
856
|
+
provider,
|
|
857
|
+
restLength,
|
|
858
|
+
connection
|
|
859
|
+
});
|
|
860
|
+
const frequency = luxon.Duration.isDuration(burstInterval) ? burstInterval : luxon.Duration.fromObject(burstInterval);
|
|
861
|
+
const length = luxon.Duration.isDuration(burstLength) ? burstLength : luxon.Duration.fromObject(burstLength);
|
|
862
|
+
await this.options.scheduler.scheduleTask({
|
|
863
|
+
id: provider.getProviderName(),
|
|
864
|
+
fn: engine.taskFn.bind(engine),
|
|
865
|
+
frequency,
|
|
866
|
+
timeout: length
|
|
867
|
+
});
|
|
868
|
+
} catch (error) {
|
|
869
|
+
logger.warn(
|
|
870
|
+
`Failed to initialize incremental ingestion provider ${provider.getProviderName()}, ${errors.stringifyError(
|
|
871
|
+
error
|
|
872
|
+
)}`
|
|
873
|
+
);
|
|
874
|
+
throw error;
|
|
875
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const incrementalIngestionEntityProviderCatalogModule = backendPluginApi.createBackendModule({
|
|
880
|
+
pluginId: "catalog",
|
|
881
|
+
moduleId: "incrementalIngestionEntityProvider",
|
|
882
|
+
register(env, options) {
|
|
883
|
+
env.registerInit({
|
|
884
|
+
deps: {
|
|
885
|
+
catalog: pluginCatalogNode.catalogProcessingExtensionPoint,
|
|
886
|
+
config: backendPluginApi.configServiceRef,
|
|
887
|
+
database: backendPluginApi.databaseServiceRef,
|
|
888
|
+
httpRouter: backendPluginApi.httpRouterServiceRef,
|
|
889
|
+
logger: backendPluginApi.loggerServiceRef,
|
|
890
|
+
scheduler: backendPluginApi.schedulerServiceRef
|
|
891
|
+
},
|
|
892
|
+
async init({
|
|
893
|
+
catalog,
|
|
894
|
+
config,
|
|
895
|
+
database,
|
|
896
|
+
httpRouter,
|
|
897
|
+
logger,
|
|
898
|
+
scheduler
|
|
899
|
+
}) {
|
|
900
|
+
const client = await database.getClient();
|
|
901
|
+
const providers = new WrapperProviders({
|
|
902
|
+
config,
|
|
903
|
+
logger,
|
|
904
|
+
client,
|
|
905
|
+
scheduler
|
|
906
|
+
});
|
|
907
|
+
for (const entry of options.providers) {
|
|
908
|
+
const wrapped = providers.wrap(entry.provider, entry.options);
|
|
909
|
+
catalog.addEntityProvider(wrapped);
|
|
910
|
+
}
|
|
911
|
+
httpRouter.use(await providers.adminRouter());
|
|
912
|
+
}
|
|
913
|
+
});
|
|
914
|
+
}
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
class IncrementalCatalogBuilder {
|
|
918
|
+
constructor(env, builder, client, manager) {
|
|
919
|
+
this.env = env;
|
|
920
|
+
this.builder = builder;
|
|
921
|
+
this.client = client;
|
|
922
|
+
this.manager = manager;
|
|
923
|
+
this.ready = new Deferred();
|
|
924
|
+
}
|
|
925
|
+
static async create(env, builder) {
|
|
926
|
+
const client = await env.database.getClient();
|
|
927
|
+
const manager = new IncrementalIngestionDatabaseManager({ client });
|
|
928
|
+
return new IncrementalCatalogBuilder(env, builder, client, manager);
|
|
929
|
+
}
|
|
930
|
+
async build() {
|
|
931
|
+
await applyDatabaseMigrations(this.client);
|
|
932
|
+
this.ready.resolve();
|
|
933
|
+
const routerLogger = this.env.logger.child({
|
|
934
|
+
router: "IncrementalProviderAdmin"
|
|
935
|
+
});
|
|
936
|
+
const incrementalAdminRouter = await createIncrementalProviderRouter(
|
|
937
|
+
this.manager,
|
|
938
|
+
routerLogger
|
|
939
|
+
);
|
|
940
|
+
return { incrementalAdminRouter };
|
|
941
|
+
}
|
|
942
|
+
addIncrementalEntityProvider(provider, options) {
|
|
943
|
+
const { burstInterval, burstLength, restLength } = options;
|
|
944
|
+
const { logger: catalogLogger, scheduler } = this.env;
|
|
945
|
+
const ready = this.ready;
|
|
946
|
+
const manager = this.manager;
|
|
947
|
+
this.builder.addEntityProvider({
|
|
948
|
+
getProviderName: provider.getProviderName.bind(provider),
|
|
949
|
+
async connect(connection) {
|
|
950
|
+
const logger = catalogLogger.child({
|
|
951
|
+
entityProvider: provider.getProviderName()
|
|
952
|
+
});
|
|
953
|
+
logger.info(`Connecting`);
|
|
954
|
+
const engine = new IncrementalIngestionEngine({
|
|
955
|
+
...options,
|
|
956
|
+
ready,
|
|
957
|
+
manager,
|
|
958
|
+
logger,
|
|
959
|
+
provider,
|
|
960
|
+
restLength,
|
|
961
|
+
connection
|
|
962
|
+
});
|
|
963
|
+
const frequency = luxon.Duration.isDuration(burstInterval) ? burstInterval : luxon.Duration.fromObject(burstInterval);
|
|
964
|
+
const length = luxon.Duration.isDuration(burstLength) ? burstLength : luxon.Duration.fromObject(burstLength);
|
|
965
|
+
await scheduler.scheduleTask({
|
|
966
|
+
id: provider.getProviderName(),
|
|
967
|
+
fn: engine.taskFn.bind(engine),
|
|
968
|
+
frequency,
|
|
969
|
+
timeout: length
|
|
970
|
+
});
|
|
971
|
+
}
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
exports.INCREMENTAL_ENTITY_PROVIDER_ANNOTATION = INCREMENTAL_ENTITY_PROVIDER_ANNOTATION;
|
|
977
|
+
exports.IncrementalCatalogBuilder = IncrementalCatalogBuilder;
|
|
978
|
+
exports.incrementalIngestionEntityProviderCatalogModule = incrementalIngestionEntityProviderCatalogModule;
|
|
979
|
+
//# sourceMappingURL=index.cjs.js.map
|