@adula/kit 0.2.0-alpha.3 → 1.0.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/README.md +12 -2
- package/build/agent/capabilities.md +49 -7
- package/build/agent/skills/idea-review/SKILL.md +26 -1
- package/build/agent/skills/module-review/SKILL.md +22 -0
- package/build/agent/skills/perf-review/SKILL.md +24 -0
- package/build/agent/skills/schema-review/SKILL.md +22 -0
- package/build/agent/skills/security-review/SKILL.md +24 -0
- package/build/agent/skills/ui-review/SKILL.md +22 -0
- package/build/commands/capabilities.d.ts +4 -0
- package/build/commands/capabilities.js +35 -4
- package/build/commands/gaps.js +5 -8
- package/build/commands/main.d.ts +4 -2
- package/build/commands/main.js +2 -0
- package/build/database/migrations/1770000000004_kit_collaboration.d.ts +5 -0
- package/build/database/migrations/1770000000004_kit_collaboration.js +10 -0
- package/build/database/migrations/1770000000005_kit_assignments.d.ts +5 -0
- package/build/database/migrations/1770000000005_kit_assignments.js +10 -0
- package/build/database/migrations/1770000000006_kit_messaging.d.ts +5 -0
- package/build/database/migrations/1770000000006_kit_messaging.js +10 -0
- package/build/database/migrations/1770000000007_kit_webhooks.d.ts +5 -0
- package/build/database/migrations/1770000000007_kit_webhooks.js +10 -0
- package/build/database/migrations/1770000000008_kit_imports.d.ts +5 -0
- package/build/database/migrations/1770000000008_kit_imports.js +10 -0
- package/build/database/migrations/1770000000010_kit_workflows.d.ts +5 -0
- package/build/database/migrations/1770000000010_kit_workflows.js +10 -0
- package/build/index.d.ts +22 -2
- package/build/index.js +13 -2
- package/build/src/admin/contracts.js +19 -10
- package/build/src/admin/controller.d.ts +1 -0
- package/build/src/admin/controller.js +6 -0
- package/build/src/admin/resource_service.d.ts +48 -3
- package/build/src/admin/resource_service.js +194 -18
- package/build/src/attachments/attachment_service.d.ts +18 -1
- package/build/src/attachments/attachment_service.js +62 -0
- package/build/src/auth/ability.d.ts +2 -0
- package/build/src/collaboration/assignments.d.ts +78 -0
- package/build/src/collaboration/assignments.js +219 -0
- package/build/src/collaboration/record_collaboration.d.ts +86 -0
- package/build/src/collaboration/record_collaboration.js +360 -0
- package/build/src/commands/agent_assets.js +5 -0
- package/build/src/commands/capabilities.d.ts +19 -0
- package/build/src/commands/capabilities.js +160 -0
- package/build/src/commands/gap_report.d.ts +8 -0
- package/build/src/commands/gap_report.js +12 -0
- package/build/src/core/message_templates.d.ts +80 -0
- package/build/src/core/message_templates.js +288 -0
- package/build/src/core/saved_views.js +31 -6
- package/build/src/core/user_invitations.js +2 -2
- package/build/src/database/schema.d.ts +18 -0
- package/build/src/database/schema.js +192 -0
- package/build/src/events/outbox.d.ts +1 -0
- package/build/src/events/outbox.js +1 -1
- package/build/src/events/record_mutation.d.ts +9 -0
- package/build/src/events/record_mutation.js +18 -3
- package/build/src/integrations/imports.d.ts +74 -0
- package/build/src/integrations/imports.js +331 -0
- package/build/src/integrations/openapi.d.ts +39 -0
- package/build/src/integrations/openapi.js +256 -0
- package/build/src/integrations/print.d.ts +37 -0
- package/build/src/integrations/print.js +123 -0
- package/build/src/integrations/webhooks.d.ts +98 -0
- package/build/src/integrations/webhooks.js +298 -0
- package/build/src/resource/define_resource.d.ts +1 -0
- package/build/src/resource/define_resource.js +9 -1
- package/build/src/resource/registry.d.ts +2 -0
- package/build/src/resource/registry.js +9 -0
- package/build/src/resource/types.d.ts +13 -1
- package/build/src/workflows/define_workflow.d.ts +95 -0
- package/build/src/workflows/define_workflow.js +83 -0
- package/build/src/workflows/engine.d.ts +119 -0
- package/build/src/workflows/engine.js +687 -0
- package/package.json +7 -13
|
@@ -5,3 +5,21 @@ export declare function createCoreSchema(db: Knex): Promise<void>;
|
|
|
5
5
|
export declare function createAttachmentsSchema(db: Knex): Promise<void>;
|
|
6
6
|
export declare function createSavedViewsSchema(db: Knex): Promise<void>;
|
|
7
7
|
export declare function createResourceTable(db: Knex, resource: Pick<Resource, 'name' | 'scoped' | 'version' | 'submittable' | 'customFields' | 'fields'>): Promise<void>;
|
|
8
|
+
/** Record collaboration (phase 3): comments with mentions, followers, tags and field history. */
|
|
9
|
+
export declare function createCollaborationSchema(db: Knex): Promise<void>;
|
|
10
|
+
/** Work assigned to a user on a record; approval steps of workflows reuse it (phase 4). */
|
|
11
|
+
export declare function createAssignmentsSchema(db: Knex): Promise<void>;
|
|
12
|
+
/**
|
|
13
|
+
* Message templates, notification e-mail delivery state and the realtime signal.
|
|
14
|
+
* The trigger's NOTIFY is delivered only when the inserting transaction commits.
|
|
15
|
+
*/
|
|
16
|
+
export declare function createMessagingSchema(db: Knex): Promise<void>;
|
|
17
|
+
/** Outgoing webhooks and their delivery log (exactly one row per webhook and event). */
|
|
18
|
+
export declare function createWebhooksSchema(db: Knex): Promise<void>;
|
|
19
|
+
/** CSV import batches: parsed rows, column mapping, progress and per-row errors. */
|
|
20
|
+
export declare function createImportsSchema(db: Knex): Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Workflow engine state (phase 4): the submission envelope rows gain execution
|
|
23
|
+
* columns, and every transition is appended to workflow_events.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createWorkflowSchema(db: Knex): Promise<void>;
|
|
@@ -246,3 +246,195 @@ export async function createResourceTable(db, resource) {
|
|
|
246
246
|
await db.raw('CREATE INDEX ?? ON ?? USING gin(search_vector)', [`${table}_search_gin`, table]);
|
|
247
247
|
}
|
|
248
248
|
}
|
|
249
|
+
/** Record collaboration (phase 3): comments with mentions, followers, tags and field history. */
|
|
250
|
+
export async function createCollaborationSchema(db) {
|
|
251
|
+
await db.schema.createTable('comments', (t) => {
|
|
252
|
+
t.bigIncrements('id');
|
|
253
|
+
t.string('resource').notNullable();
|
|
254
|
+
t.integer('record_id').notNullable();
|
|
255
|
+
t.integer('author_id').notNullable().references('id').inTable('users').onDelete('RESTRICT');
|
|
256
|
+
t.text('body').notNullable();
|
|
257
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
258
|
+
t.timestamp('edited_at', { useTz: true });
|
|
259
|
+
t.timestamp('deleted_at', { useTz: true });
|
|
260
|
+
t.index(['resource', 'record_id', 'id']);
|
|
261
|
+
});
|
|
262
|
+
await db.schema.createTable('comment_mentions', (t) => {
|
|
263
|
+
t.bigInteger('comment_id')
|
|
264
|
+
.notNullable()
|
|
265
|
+
.references('id')
|
|
266
|
+
.inTable('comments')
|
|
267
|
+
.onDelete('CASCADE');
|
|
268
|
+
t.integer('user_id').notNullable().references('id').inTable('users').onDelete('CASCADE');
|
|
269
|
+
t.primary(['comment_id', 'user_id']);
|
|
270
|
+
t.index(['user_id']);
|
|
271
|
+
});
|
|
272
|
+
await db.schema.createTable('followers', (t) => {
|
|
273
|
+
t.string('resource').notNullable();
|
|
274
|
+
t.integer('record_id').notNullable();
|
|
275
|
+
t.integer('user_id').notNullable().references('id').inTable('users').onDelete('CASCADE');
|
|
276
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
277
|
+
t.primary(['resource', 'record_id', 'user_id']);
|
|
278
|
+
t.index(['user_id']);
|
|
279
|
+
});
|
|
280
|
+
await db.schema.createTable('tags', (t) => {
|
|
281
|
+
t.increments('id');
|
|
282
|
+
t.string('name', 60).notNullable().unique();
|
|
283
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
284
|
+
});
|
|
285
|
+
await db.schema.createTable('taggables', (t) => {
|
|
286
|
+
t.integer('tag_id').notNullable().references('id').inTable('tags').onDelete('CASCADE');
|
|
287
|
+
t.string('resource').notNullable();
|
|
288
|
+
t.integer('record_id').notNullable();
|
|
289
|
+
t.primary(['tag_id', 'resource', 'record_id']);
|
|
290
|
+
t.index(['resource', 'record_id']);
|
|
291
|
+
});
|
|
292
|
+
await db.schema.createTable('field_changes', (t) => {
|
|
293
|
+
t.bigIncrements('id');
|
|
294
|
+
t.bigInteger('activity_id')
|
|
295
|
+
.notNullable()
|
|
296
|
+
.references('id')
|
|
297
|
+
.inTable('activities')
|
|
298
|
+
.onDelete('CASCADE')
|
|
299
|
+
.index();
|
|
300
|
+
t.string('resource').notNullable();
|
|
301
|
+
t.integer('record_id').notNullable();
|
|
302
|
+
t.string('field').notNullable();
|
|
303
|
+
t.jsonb('before');
|
|
304
|
+
t.jsonb('after');
|
|
305
|
+
t.index(['resource', 'record_id', 'id']);
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
/** Work assigned to a user on a record; approval steps of workflows reuse it (phase 4). */
|
|
309
|
+
export async function createAssignmentsSchema(db) {
|
|
310
|
+
await db.schema.createTable('assignments', (t) => {
|
|
311
|
+
t.bigIncrements('id');
|
|
312
|
+
t.string('resource').notNullable();
|
|
313
|
+
t.integer('record_id').notNullable();
|
|
314
|
+
t.integer('assignee_id').notNullable().references('id').inTable('users').onDelete('RESTRICT');
|
|
315
|
+
t.integer('assigned_by').references('id').inTable('users').onDelete('RESTRICT');
|
|
316
|
+
t.string('kind', 20).notNullable().defaultTo('task');
|
|
317
|
+
t.string('title', 200).notNullable();
|
|
318
|
+
t.text('note');
|
|
319
|
+
t.date('due_on');
|
|
320
|
+
t.string('status', 20).notNullable().defaultTo('open');
|
|
321
|
+
t.uuid('workflow_run_id');
|
|
322
|
+
t.string('workflow_step', 100);
|
|
323
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
324
|
+
t.timestamp('completed_at', { useTz: true });
|
|
325
|
+
t.integer('completed_by').references('id').inTable('users').onDelete('RESTRICT');
|
|
326
|
+
t.string('outcome', 20);
|
|
327
|
+
t.index(['assignee_id', 'status', 'id']);
|
|
328
|
+
t.index(['resource', 'record_id']);
|
|
329
|
+
t.index(['workflow_run_id']);
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Message templates, notification e-mail delivery state and the realtime signal.
|
|
334
|
+
* The trigger's NOTIFY is delivered only when the inserting transaction commits.
|
|
335
|
+
*/
|
|
336
|
+
export async function createMessagingSchema(db) {
|
|
337
|
+
await db.schema.createTable('message_templates', (t) => {
|
|
338
|
+
t.string('key', 100).primary();
|
|
339
|
+
t.string('subject', 255).notNullable();
|
|
340
|
+
t.text('body').notNullable();
|
|
341
|
+
t.boolean('mail').notNullable().defaultTo(false);
|
|
342
|
+
t.integer('updated_by').references('id').inTable('users').onDelete('SET NULL');
|
|
343
|
+
t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
344
|
+
});
|
|
345
|
+
await db.schema.alterTable('notifications', (t) => {
|
|
346
|
+
t.string('template_key', 100);
|
|
347
|
+
t.string('mail_state', 20);
|
|
348
|
+
t.integer('mail_attempts').notNullable().defaultTo(0);
|
|
349
|
+
t.string('mail_error', 500);
|
|
350
|
+
t.timestamp('mailed_at', { useTz: true });
|
|
351
|
+
});
|
|
352
|
+
await db.raw("CREATE INDEX notifications_mail_pending ON notifications (id) WHERE mail_state = 'pending'");
|
|
353
|
+
await db.raw(`CREATE FUNCTION kit_notification_signal() RETURNS trigger LANGUAGE plpgsql AS $$ BEGIN PERFORM pg_notify('kit_notifications', json_build_object('userId', NEW.user_id, 'id', NEW.id)::text); RETURN NULL; END $$`);
|
|
354
|
+
await db.raw('CREATE TRIGGER kit_notification_signal AFTER INSERT ON notifications FOR EACH ROW EXECUTE FUNCTION kit_notification_signal()');
|
|
355
|
+
}
|
|
356
|
+
/** Outgoing webhooks and their delivery log (exactly one row per webhook and event). */
|
|
357
|
+
export async function createWebhooksSchema(db) {
|
|
358
|
+
await db.schema.createTable('webhooks', (t) => {
|
|
359
|
+
t.increments('id');
|
|
360
|
+
t.string('name', 100).notNullable();
|
|
361
|
+
t.string('url', 2000).notNullable();
|
|
362
|
+
t.text('secret').notNullable();
|
|
363
|
+
t.jsonb('events').notNullable();
|
|
364
|
+
t.boolean('active').notNullable().defaultTo(true);
|
|
365
|
+
t.integer('failing').notNullable().defaultTo(0);
|
|
366
|
+
t.integer('created_by').references('id').inTable('users').onDelete('SET NULL');
|
|
367
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
368
|
+
t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
369
|
+
t.timestamp('last_delivery_at', { useTz: true });
|
|
370
|
+
});
|
|
371
|
+
await db.raw('CREATE INDEX webhooks_events_gin ON webhooks USING gin(events)');
|
|
372
|
+
await db.schema.createTable('webhook_deliveries', (t) => {
|
|
373
|
+
t.uuid('id').primary();
|
|
374
|
+
t.integer('webhook_id').notNullable().references('id').inTable('webhooks').onDelete('CASCADE');
|
|
375
|
+
t.uuid('event_id').notNullable();
|
|
376
|
+
t.string('event').notNullable();
|
|
377
|
+
t.jsonb('payload').notNullable();
|
|
378
|
+
t.string('status', 20).notNullable().defaultTo('pending');
|
|
379
|
+
t.integer('attempts').notNullable().defaultTo(0);
|
|
380
|
+
t.integer('last_status');
|
|
381
|
+
t.string('last_error', 500);
|
|
382
|
+
t.timestamp('next_attempt_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
383
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
384
|
+
t.timestamp('delivered_at', { useTz: true });
|
|
385
|
+
t.unique(['webhook_id', 'event_id']);
|
|
386
|
+
t.index(['webhook_id', 'created_at']);
|
|
387
|
+
});
|
|
388
|
+
await db.raw("CREATE INDEX webhook_deliveries_due ON webhook_deliveries (next_attempt_at) WHERE status = 'pending'");
|
|
389
|
+
}
|
|
390
|
+
/** CSV import batches: parsed rows, column mapping, progress and per-row errors. */
|
|
391
|
+
export async function createImportsSchema(db) {
|
|
392
|
+
await db.schema.createTable('import_batches', (t) => {
|
|
393
|
+
t.increments('id');
|
|
394
|
+
t.string('resource').notNullable();
|
|
395
|
+
t.integer('user_id').notNullable().references('id').inTable('users').onDelete('CASCADE');
|
|
396
|
+
t.string('file_name', 200).notNullable();
|
|
397
|
+
t.string('status', 20).notNullable();
|
|
398
|
+
t.jsonb('headers').notNullable();
|
|
399
|
+
t.jsonb('mapping').notNullable();
|
|
400
|
+
t.jsonb('rows').notNullable();
|
|
401
|
+
t.integer('total').notNullable();
|
|
402
|
+
t.integer('processed').notNullable().defaultTo(0);
|
|
403
|
+
t.integer('created').notNullable().defaultTo(0);
|
|
404
|
+
t.integer('failed').notNullable().defaultTo(0);
|
|
405
|
+
t.jsonb('errors').notNullable().defaultTo('[]');
|
|
406
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
407
|
+
t.timestamp('started_at', { useTz: true });
|
|
408
|
+
t.timestamp('finished_at', { useTz: true });
|
|
409
|
+
t.index(['user_id', 'id']);
|
|
410
|
+
t.index(['status']);
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* Workflow engine state (phase 4): the submission envelope rows gain execution
|
|
415
|
+
* columns, and every transition is appended to workflow_events.
|
|
416
|
+
*/
|
|
417
|
+
export async function createWorkflowSchema(db) {
|
|
418
|
+
await db.schema.alterTable('workflow_runs', (t) => {
|
|
419
|
+
t.string('current_step', 100);
|
|
420
|
+
t.timestamp('wake_at', { useTz: true });
|
|
421
|
+
t.integer('attempts').notNullable().defaultTo(0);
|
|
422
|
+
t.string('last_error', 1000);
|
|
423
|
+
t.string('outcome', 30);
|
|
424
|
+
t.integer('started_by').references('id').inTable('users').onDelete('SET NULL');
|
|
425
|
+
t.timestamp('updated_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
426
|
+
t.timestamp('completed_at', { useTz: true });
|
|
427
|
+
t.index(['resource', 'record_id']);
|
|
428
|
+
});
|
|
429
|
+
await db.raw("CREATE INDEX workflow_runs_due ON workflow_runs (wake_at) WHERE status = 'running'");
|
|
430
|
+
await db.schema.createTable('workflow_events', (t) => {
|
|
431
|
+
t.bigIncrements('id');
|
|
432
|
+
t.uuid('run_id').notNullable().references('id').inTable('workflow_runs').onDelete('CASCADE');
|
|
433
|
+
t.string('step', 100);
|
|
434
|
+
t.string('event', 50).notNullable();
|
|
435
|
+
t.integer('actor_id').references('id').inTable('users').onDelete('SET NULL');
|
|
436
|
+
t.jsonb('detail').notNullable().defaultTo('{}');
|
|
437
|
+
t.timestamp('created_at', { useTz: true }).notNullable().defaultTo(db.fn.now());
|
|
438
|
+
t.index(['run_id', 'id']);
|
|
439
|
+
});
|
|
440
|
+
}
|
|
@@ -17,7 +17,7 @@ export async function publishOutbox(db, jobs, limit = 100) {
|
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
19
|
export async function consumeEvent(db, listener, event) {
|
|
20
|
-
if (listener.event !== event.event)
|
|
20
|
+
if (listener.event !== '*' && listener.event !== event.event)
|
|
21
21
|
return false;
|
|
22
22
|
return db.transaction(async (trx) => {
|
|
23
23
|
const inserted = await trx('processed_events')
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { Knex } from 'knex';
|
|
2
2
|
import type { Action } from '../resource/types.js';
|
|
3
|
+
export type FieldChange = {
|
|
4
|
+
field: string;
|
|
5
|
+
before: unknown;
|
|
6
|
+
after: unknown;
|
|
7
|
+
};
|
|
3
8
|
/** Infrastructure writes for a module-owned mutation, inside its existing transaction. */
|
|
4
9
|
export declare function recordMutation(trx: Knex.Transaction, mutation: {
|
|
5
10
|
module: string;
|
|
6
11
|
resource: string;
|
|
7
12
|
id: unknown;
|
|
8
13
|
actorId: number;
|
|
14
|
+
/** The administrator acting as actorId, when impersonating. */
|
|
15
|
+
impersonatorId?: number;
|
|
9
16
|
action: Action;
|
|
10
17
|
fields: string[];
|
|
18
|
+
/** Before/after values of the changed fields; read back per viewer's field access. */
|
|
19
|
+
changes?: FieldChange[];
|
|
11
20
|
}): Promise<void>;
|
|
@@ -17,14 +17,29 @@ export async function recordMutation(trx, mutation) {
|
|
|
17
17
|
resource: mutation.resource,
|
|
18
18
|
id: mutation.id,
|
|
19
19
|
actorId: mutation.actorId,
|
|
20
|
+
...(mutation.impersonatorId ? { impersonatorId: mutation.impersonatorId } : {}),
|
|
20
21
|
};
|
|
21
|
-
await trx('activities')
|
|
22
|
+
const [activity] = await trx('activities')
|
|
23
|
+
.insert({
|
|
22
24
|
resource: mutation.resource,
|
|
23
25
|
record_id: mutation.id,
|
|
24
26
|
actor_id: mutation.actorId,
|
|
25
27
|
action: mutation.action,
|
|
26
|
-
changes: JSON.stringify({
|
|
27
|
-
|
|
28
|
+
changes: JSON.stringify({
|
|
29
|
+
fields: mutation.fields,
|
|
30
|
+
...(mutation.impersonatorId ? { impersonatedBy: mutation.impersonatorId } : {}),
|
|
31
|
+
}),
|
|
32
|
+
})
|
|
33
|
+
.returning('id');
|
|
34
|
+
if (mutation.changes?.length)
|
|
35
|
+
await trx('field_changes').insert(mutation.changes.map((change) => ({
|
|
36
|
+
activity_id: activity.id,
|
|
37
|
+
resource: mutation.resource,
|
|
38
|
+
record_id: mutation.id,
|
|
39
|
+
field: change.field,
|
|
40
|
+
before: JSON.stringify(change.before ?? null),
|
|
41
|
+
after: JSON.stringify(change.after ?? null),
|
|
42
|
+
})));
|
|
28
43
|
await trx('outbox').insert({
|
|
29
44
|
id: eventId,
|
|
30
45
|
event,
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { Knex } from 'knex';
|
|
2
|
+
import type { Actor } from '../auth/ability.js';
|
|
3
|
+
import type { ResourceService } from '../admin/resource_service.js';
|
|
4
|
+
import type { ResourceRegistry } from '../resource/registry.js';
|
|
5
|
+
import type { Field } from '../resource/types.js';
|
|
6
|
+
export type ImportStatus = 'mapping' | 'queued' | 'running' | 'done' | 'failed';
|
|
7
|
+
export type ImportTarget = {
|
|
8
|
+
key: string;
|
|
9
|
+
label: string;
|
|
10
|
+
type: string;
|
|
11
|
+
required: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type ImportBatch = {
|
|
14
|
+
id: number;
|
|
15
|
+
resource: string;
|
|
16
|
+
resourceLabel: string;
|
|
17
|
+
fileName: string;
|
|
18
|
+
status: ImportStatus;
|
|
19
|
+
headers: string[];
|
|
20
|
+
mapping: Record<string, string>;
|
|
21
|
+
sample: string[][];
|
|
22
|
+
total: number;
|
|
23
|
+
processed: number;
|
|
24
|
+
created: number;
|
|
25
|
+
failed: number;
|
|
26
|
+
errors: {
|
|
27
|
+
row: number;
|
|
28
|
+
message: string;
|
|
29
|
+
}[];
|
|
30
|
+
createdAt: string;
|
|
31
|
+
finishedAt: string | null;
|
|
32
|
+
targets: ImportTarget[];
|
|
33
|
+
};
|
|
34
|
+
export type ActorSource = {
|
|
35
|
+
load(id: number): Promise<Actor>;
|
|
36
|
+
};
|
|
37
|
+
export declare const IMPORT_ROW_LIMIT = 5000;
|
|
38
|
+
/** Converts one CSV cell to the field's stored representation, or throws a readable error. */
|
|
39
|
+
export declare function importCell(field: Field, raw: string, lookups: Map<string, string>): unknown;
|
|
40
|
+
/**
|
|
41
|
+
* CSV imports in batches: the upload is parsed and stored, the user maps columns
|
|
42
|
+
* to writable fields, and a worker saves each row through ResourceService with
|
|
43
|
+
* the importing user's current permissions. Row failures never stop the batch.
|
|
44
|
+
*/
|
|
45
|
+
export declare class ImportBatches {
|
|
46
|
+
private db;
|
|
47
|
+
private registry;
|
|
48
|
+
private resources;
|
|
49
|
+
private actors;
|
|
50
|
+
constructor(db: Knex, registry: ResourceRegistry, resources: ResourceService, actors: ActorSource);
|
|
51
|
+
/** Writable, importable fields for this actor. */
|
|
52
|
+
targets(name: string, actor: Actor): ImportTarget[];
|
|
53
|
+
create(name: string, actor: Actor, input: {
|
|
54
|
+
fileName: unknown;
|
|
55
|
+
content: unknown;
|
|
56
|
+
}): Promise<ImportBatch>;
|
|
57
|
+
show(id: unknown, actor: Actor): Promise<ImportBatch>;
|
|
58
|
+
list(actor: Actor): Promise<ImportBatch[]>;
|
|
59
|
+
/** Stores the column mapping and queues the batch for the worker. */
|
|
60
|
+
start(id: unknown, actor: Actor, mapping: unknown): Promise<ImportBatch>;
|
|
61
|
+
/**
|
|
62
|
+
* Worker step: claims one queued or running batch and imports up to `chunk`
|
|
63
|
+
* rows. Progress is committed per row, so a crash resumes after the last one.
|
|
64
|
+
*/
|
|
65
|
+
process(chunk?: number): Promise<{
|
|
66
|
+
id: number;
|
|
67
|
+
processed: number;
|
|
68
|
+
created: number;
|
|
69
|
+
failed: number;
|
|
70
|
+
} | null>;
|
|
71
|
+
private safeTargets;
|
|
72
|
+
private owned;
|
|
73
|
+
private present;
|
|
74
|
+
}
|