@chkit/plugin-ingest 0.2.0-beta.11
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 +43 -0
- package/dist/destination.d.ts +40 -0
- package/dist/destination.d.ts.map +1 -0
- package/dist/destination.js +65 -0
- package/dist/destination.js.map +1 -0
- package/dist/errors.d.ts +33 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +133 -0
- package/dist/errors.js.map +1 -0
- package/dist/executor.d.ts +30 -0
- package/dist/executor.d.ts.map +1 -0
- package/dist/executor.js +552 -0
- package/dist/executor.js.map +1 -0
- package/dist/incremental.d.ts +45 -0
- package/dist/incremental.d.ts.map +1 -0
- package/dist/incremental.js +72 -0
- package/dist/incremental.js.map +1 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/journal-history.d.ts +15 -0
- package/dist/journal-history.d.ts.map +1 -0
- package/dist/journal-history.js +366 -0
- package/dist/journal-history.js.map +1 -0
- package/dist/journal-records.d.ts +28 -0
- package/dist/journal-records.d.ts.map +1 -0
- package/dist/journal-records.js +76 -0
- package/dist/journal-records.js.map +1 -0
- package/dist/journal.d.ts +16 -0
- package/dist/journal.d.ts.map +1 -0
- package/dist/journal.js +72 -0
- package/dist/journal.js.map +1 -0
- package/dist/loader.d.ts +12 -0
- package/dist/loader.d.ts.map +1 -0
- package/dist/loader.js +37 -0
- package/dist/loader.js.map +1 -0
- package/dist/paginate.d.ts +24 -0
- package/dist/paginate.d.ts.map +1 -0
- package/dist/paginate.js +37 -0
- package/dist/paginate.js.map +1 -0
- package/dist/plugin.d.ts +155 -0
- package/dist/plugin.d.ts.map +1 -0
- package/dist/plugin.js +353 -0
- package/dist/plugin.js.map +1 -0
- package/dist/queue.d.ts +9 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +47 -0
- package/dist/queue.js.map +1 -0
- package/dist/recovery.d.ts +41 -0
- package/dist/recovery.d.ts.map +1 -0
- package/dist/recovery.js +145 -0
- package/dist/recovery.js.map +1 -0
- package/dist/registry.d.ts +48 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +109 -0
- package/dist/registry.js.map +1 -0
- package/dist/retry.d.ts +12 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +69 -0
- package/dist/retry.js.map +1 -0
- package/dist/testing.d.ts +18 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +54 -0
- package/dist/testing.js.map +1 -0
- package/dist/types.d.ts +229 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @chkit/plugin-ingest
|
|
2
|
+
|
|
3
|
+
`@chkit/plugin-ingest` implements **chkit Sync (beta)**, the optional TypeScript layer for syncing business application data into ClickHouse. Define and migrate destination tables with [chkit](https://www.npmjs.com/package/chkit), then add readers to load them. Use an external scheduler for repeated runs.
|
|
4
|
+
|
|
5
|
+
The executor records progress after writes succeed. Run-scoped journal identities keep overlapping executions independent. A retry resumes from a validated checkpoint and may reread rows.
|
|
6
|
+
|
|
7
|
+
`paginate()` yields complete `{ items, next, metadata? }` pages, including empty terminal pages. Read rows from `page.items`; use `next` for request continuation and explicitly map checkpoint metadata into a chunk's `state` with `cursorState`. The executor commits that state after the rows are saved. This replaces the previous item-array return type; existing readers must use `page.items`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { defineConfig } from '@chkit/core'
|
|
11
|
+
import { ingest } from '@chkit/plugin-ingest'
|
|
12
|
+
|
|
13
|
+
export default defineConfig({
|
|
14
|
+
entry: './src/chkit.ts',
|
|
15
|
+
plugins: [ingest()],
|
|
16
|
+
clickhouse: { url: process.env.CLICKHOUSE_URL ?? '' },
|
|
17
|
+
})
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```sh
|
|
21
|
+
chkit ingest run --tag schedule:1h
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
The append-only ClickHouse journal records each run's starting checkpoint and local event sequence. Two processes may start from the same checkpoint without reusing event identities. Complete snapshot validation excludes malformed run tails while retaining acknowledged progress. A stale but valid read can cause replay; destination writes remain at-least-once.
|
|
25
|
+
|
|
26
|
+
Diagnose the journal or preview evidence-preserving recovery:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
chkit ingest doctor --tag stream:helpdesk.tickets
|
|
30
|
+
chkit ingest repair --tag stream:helpdesk.tickets
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Overlapping valid runs need no repair. For damaged evidence, stop all ingestion writers and review the plan. `chkit ingest repair --tag stream:helpdesk.tickets --apply <fingerprint>` materializes a verified archive and replacement journal while preserving the original. Keep writers stopped, set `ingest({ journalTable: '<replacementTable>' })` to the returned table, then restart them. The command leaves activation to this explicit configuration change. Other streams' evidence is copied unchanged.
|
|
34
|
+
|
|
35
|
+
Documentation: [chkit Sync (beta)](https://chkit.obsessiondb.com/sync/)
|
|
36
|
+
|
|
37
|
+
For raw storage, typed rows, and where to map fields, see [Destinations and transformations](https://chkit.obsessiondb.com/api-sync/destinations/).
|
|
38
|
+
|
|
39
|
+
Install the source-authoring skill for a coding agent:
|
|
40
|
+
|
|
41
|
+
```sh
|
|
42
|
+
npx skills add obsessiondb/chkit --skill chkit-ingestion
|
|
43
|
+
```
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ClickHouseExecutor } from '@chkit/clickhouse';
|
|
2
|
+
import { type ColumnDefinition, type TableDefinition } from '@chkit/core';
|
|
3
|
+
import type { DestinationAdapter, Row } from './types.js';
|
|
4
|
+
export declare const BATCH_ID_COLUMN = "_chkit_batch_id";
|
|
5
|
+
export declare const RUN_ID_COLUMN = "_chkit_run_id";
|
|
6
|
+
export declare const INGESTED_AT_COLUMN = "_chkit_ingested_at";
|
|
7
|
+
/**
|
|
8
|
+
* Runtime-owned metadata every ingestion destination table carries. Spread it
|
|
9
|
+
* into the table's `columns`. `_chkit_ingested_at` is destination-owned: it is
|
|
10
|
+
* never sent by the loader, so it records the physical publication time even
|
|
11
|
+
* when a retry recomputes it under the same deduplication token.
|
|
12
|
+
*/
|
|
13
|
+
export declare const ingestionColumns: readonly ColumnDefinition[];
|
|
14
|
+
export type RawRow = {
|
|
15
|
+
id: string;
|
|
16
|
+
raw: unknown;
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Landing table for provider objects exactly as received: a stable id plus the
|
|
20
|
+
* untouched object in a native JSON column. Typed shapes are derived from it
|
|
21
|
+
* inside ClickHouse (views or materialized views), so changing a transform
|
|
22
|
+
* never requires re-fetching the source. Replays and overlapping windows
|
|
23
|
+
* collapse to the latest ingested version of each id.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rawTable(input: {
|
|
26
|
+
database: string;
|
|
27
|
+
name: string;
|
|
28
|
+
comment?: string;
|
|
29
|
+
}): TableDefinition;
|
|
30
|
+
/** Shape provider objects for a {@link rawTable} without mapping their fields. */
|
|
31
|
+
export declare function rawRows<T>(items: readonly T[], id: (item: T) => string): RawRow[];
|
|
32
|
+
/**
|
|
33
|
+
* A successful synchronous insert response (or an awaited async insert) is the
|
|
34
|
+
* sink evidence ChKit trusts. A missing response stays ambiguous and is retried
|
|
35
|
+
* with the same token.
|
|
36
|
+
*/
|
|
37
|
+
export declare function createClickHouseDestination(executor: ClickHouseExecutor): DestinationAdapter;
|
|
38
|
+
/** JSONEachRow encodes big integers as exact decimal strings, including nested values. */
|
|
39
|
+
export declare function toJsonRows(rows: readonly Row[]): Row[];
|
|
40
|
+
//# sourceMappingURL=destination.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destination.d.ts","sourceRoot":"","sources":["../src/destination.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAE,KAAK,eAAe,EAAE,MAAM,aAAa,CAAA;AAElG,OAAO,KAAK,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,YAAY,CAAA;AAEzD,eAAO,MAAM,eAAe,oBAAoB,CAAA;AAChD,eAAO,MAAM,aAAa,kBAAkB,CAAA;AAC5C,eAAO,MAAM,kBAAkB,uBAAuB,CAAA;AAEtD;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,EAAE,SAAS,gBAAgB,EAIvD,CAAA;AAGD,MAAM,MAAM,MAAM,GAAG;IACnB,EAAE,EAAE,MAAM,CAAA;IACV,GAAG,EAAE,OAAO,CAAA;CACb,CAAA;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,eAAe,CAUrG;AAED,kFAAkF;AAClF,wBAAgB,OAAO,CAAC,CAAC,EAAE,KAAK,EAAE,SAAS,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,MAAM,GAAG,MAAM,EAAE,CAEjF;AAED;;;;GAIG;AACH,wBAAgB,2BAA2B,CAAC,QAAQ,EAAE,kBAAkB,GAAG,kBAAkB,CAgB5F;AAED,0FAA0F;AAC1F,wBAAgB,UAAU,CAAC,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,GAAG,EAAE,CAEtD"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { insertColumnList, table } from '@chkit/core';
|
|
2
|
+
export const BATCH_ID_COLUMN = '_chkit_batch_id';
|
|
3
|
+
export const RUN_ID_COLUMN = '_chkit_run_id';
|
|
4
|
+
export const INGESTED_AT_COLUMN = '_chkit_ingested_at';
|
|
5
|
+
/**
|
|
6
|
+
* Runtime-owned metadata every ingestion destination table carries. Spread it
|
|
7
|
+
* into the table's `columns`. `_chkit_ingested_at` is destination-owned: it is
|
|
8
|
+
* never sent by the loader, so it records the physical publication time even
|
|
9
|
+
* when a retry recomputes it under the same deduplication token.
|
|
10
|
+
*/
|
|
11
|
+
export const ingestionColumns = [
|
|
12
|
+
{ name: BATCH_ID_COLUMN, type: 'String' },
|
|
13
|
+
{ name: RUN_ID_COLUMN, type: 'String' },
|
|
14
|
+
{ name: INGESTED_AT_COLUMN, type: "DateTime64(6, 'UTC')", default: 'fn:now64(6)' },
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Landing table for provider objects exactly as received: a stable id plus the
|
|
18
|
+
* untouched object in a native JSON column. Typed shapes are derived from it
|
|
19
|
+
* inside ClickHouse (views or materialized views), so changing a transform
|
|
20
|
+
* never requires re-fetching the source. Replays and overlapping windows
|
|
21
|
+
* collapse to the latest ingested version of each id.
|
|
22
|
+
*/
|
|
23
|
+
export function rawTable(input) {
|
|
24
|
+
return table({
|
|
25
|
+
database: input.database,
|
|
26
|
+
name: input.name,
|
|
27
|
+
comment: input.comment,
|
|
28
|
+
columns: [{ name: 'id', type: 'String' }, { name: 'raw', type: 'JSON' }, ...ingestionColumns],
|
|
29
|
+
engine: `ReplacingMergeTree(${INGESTED_AT_COLUMN})`,
|
|
30
|
+
primaryKey: ['id'],
|
|
31
|
+
orderBy: ['id'],
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/** Shape provider objects for a {@link rawTable} without mapping their fields. */
|
|
35
|
+
export function rawRows(items, id) {
|
|
36
|
+
return items.map((item) => ({ id: id(item), raw: item }));
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* A successful synchronous insert response (or an awaited async insert) is the
|
|
40
|
+
* sink evidence ChKit trusts. A missing response stays ambiguous and is retried
|
|
41
|
+
* with the same token.
|
|
42
|
+
*/
|
|
43
|
+
export function createClickHouseDestination(executor) {
|
|
44
|
+
return {
|
|
45
|
+
async insert({ table, rows, token }) {
|
|
46
|
+
if (rows.length === 0)
|
|
47
|
+
return;
|
|
48
|
+
const columns = insertColumnList(table);
|
|
49
|
+
await executor.insert({
|
|
50
|
+
table: `${table.database}.${table.name}`,
|
|
51
|
+
values: toJsonRows(rows),
|
|
52
|
+
...(columns ? { columns } : {}),
|
|
53
|
+
settings: {
|
|
54
|
+
insert_deduplication_token: token,
|
|
55
|
+
wait_for_async_insert: 1,
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/** JSONEachRow encodes big integers as exact decimal strings, including nested values. */
|
|
62
|
+
export function toJsonRows(rows) {
|
|
63
|
+
return JSON.parse(JSON.stringify(rows, (_key, value) => typeof value === 'bigint' ? value.toString() : value));
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=destination.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"destination.js","sourceRoot":"","sources":["../src/destination.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAA+C,MAAM,aAAa,CAAA;AAIlG,MAAM,CAAC,MAAM,eAAe,GAAG,iBAAiB,CAAA;AAChD,MAAM,CAAC,MAAM,aAAa,GAAG,eAAe,CAAA;AAC5C,MAAM,CAAC,MAAM,kBAAkB,GAAG,oBAAoB,CAAA;AAEtD;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAgC;IAC3D,EAAE,IAAI,EAAE,eAAe,EAAE,IAAI,EAAE,QAAQ,EAAE;IACzC,EAAE,IAAI,EAAE,aAAa,EAAE,IAAI,EAAE,QAAQ,EAAE;IACvC,EAAE,IAAI,EAAE,kBAAkB,EAAE,IAAI,EAAE,sBAAsB,EAAE,OAAO,EAAE,aAAa,EAAE;CACnF,CAAA;AAQD;;;;;;GAMG;AACH,MAAM,UAAU,QAAQ,CAAC,KAA2D;IAClF,OAAO,KAAK,CAAC;QACX,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,GAAG,gBAAgB,CAAC;QAC7F,MAAM,EAAE,sBAAsB,kBAAkB,GAAG;QACnD,UAAU,EAAE,CAAC,IAAI,CAAC;QAClB,OAAO,EAAE,CAAC,IAAI,CAAC;KAChB,CAAC,CAAA;AACJ,CAAC;AAED,kFAAkF;AAClF,MAAM,UAAU,OAAO,CAAI,KAAmB,EAAE,EAAuB;IACrE,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,2BAA2B,CAAC,QAA4B;IACtE,OAAO;QACL,KAAK,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE;YACjC,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAM;YAC7B,MAAM,OAAO,GAAG,gBAAgB,CAAC,KAAK,CAAC,CAAA;YACvC,MAAM,QAAQ,CAAC,MAAM,CAAC;gBACpB,KAAK,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE;gBACxC,MAAM,EAAE,UAAU,CAAC,IAAI,CAAC;gBACxB,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC/B,QAAQ,EAAE;oBACR,0BAA0B,EAAE,KAAK;oBACjC,qBAAqB,EAAE,CAAC;iBACzB;aACF,CAAC,CAAA;QACJ,CAAC;KACF,CAAA;AACH,CAAC;AAED,0FAA0F;AAC1F,MAAM,UAAU,UAAU,CAAC,IAAoB;IAC7C,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,KAAc,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAU,CAAA;AAClI,CAAC"}
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { ErrorClassifier, FailureClass } from './types.js';
|
|
2
|
+
export declare class IngestConfigError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Canonical boundary for fetch-based readers when `response.ok` is false.
|
|
7
|
+
* Keeps normalized HTTP facts plus the original Response for raw inspection.
|
|
8
|
+
*/
|
|
9
|
+
export declare class HttpError extends Error {
|
|
10
|
+
readonly status: number;
|
|
11
|
+
readonly statusText: string;
|
|
12
|
+
readonly url: string;
|
|
13
|
+
readonly retryAfterMs: number | undefined;
|
|
14
|
+
readonly body: string;
|
|
15
|
+
readonly response: Response;
|
|
16
|
+
private constructor();
|
|
17
|
+
static fromResponse(response: Response): Promise<HttpError>;
|
|
18
|
+
}
|
|
19
|
+
/** Executor-owned failure that always preserves the exact thrown value. */
|
|
20
|
+
export declare class FetchFailure extends Error {
|
|
21
|
+
readonly classification: FailureClass;
|
|
22
|
+
readonly cause: unknown;
|
|
23
|
+
constructor(cause: unknown, classification: FailureClass);
|
|
24
|
+
}
|
|
25
|
+
export declare class BudgetExhausted extends Error {
|
|
26
|
+
constructor(message: string);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Cancellation is authoritative, then the common HTTP/network fallback is
|
|
30
|
+
* derived, then an optional provider classifier may enrich or override it.
|
|
31
|
+
*/
|
|
32
|
+
export declare function classifyFailure(cause: unknown, signal: AbortSignal, classifier: ErrorClassifier | undefined): FailureClass;
|
|
33
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAiB/D,qBAAa,iBAAkB,SAAQ,KAAK;gBAC9B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CAAA;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAA;IAE3B,OAAO;WAWM,YAAY,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAAC,SAAS,CAAC;CAIlE;AAED,2EAA2E;AAC3E,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAA;IACrC,SAAkB,KAAK,EAAE,OAAO,CAAA;gBAEpB,KAAK,EAAE,OAAO,EAAE,cAAc,EAAE,YAAY;CAMzD;AAED,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,MAAM,EAAE,WAAW,EACnB,UAAU,EAAE,eAAe,GAAG,SAAS,GACtC,YAAY,CAId"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const DIAGNOSTIC_BODY_LIMIT = 2048;
|
|
2
|
+
const TRANSIENT_NETWORK_CODES = new Set([
|
|
3
|
+
'ECONNRESET',
|
|
4
|
+
'ECONNREFUSED',
|
|
5
|
+
'ECONNABORTED',
|
|
6
|
+
'ETIMEDOUT',
|
|
7
|
+
'EPIPE',
|
|
8
|
+
'EAI_AGAIN',
|
|
9
|
+
'ENOTFOUND',
|
|
10
|
+
'UND_ERR_SOCKET',
|
|
11
|
+
'UND_ERR_CONNECT_TIMEOUT',
|
|
12
|
+
'UND_ERR_HEADERS_TIMEOUT',
|
|
13
|
+
'UND_ERR_BODY_TIMEOUT',
|
|
14
|
+
]);
|
|
15
|
+
export class IngestConfigError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = 'IngestConfigError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Canonical boundary for fetch-based readers when `response.ok` is false.
|
|
23
|
+
* Keeps normalized HTTP facts plus the original Response for raw inspection.
|
|
24
|
+
*/
|
|
25
|
+
export class HttpError extends Error {
|
|
26
|
+
status;
|
|
27
|
+
statusText;
|
|
28
|
+
url;
|
|
29
|
+
retryAfterMs;
|
|
30
|
+
body;
|
|
31
|
+
response;
|
|
32
|
+
constructor(response, body) {
|
|
33
|
+
super(`HTTP ${response.status} ${response.statusText} for ${redactUrl(response.url)}${body ? `: ${body}` : ''}`);
|
|
34
|
+
this.name = 'HttpError';
|
|
35
|
+
this.status = response.status;
|
|
36
|
+
this.statusText = response.statusText;
|
|
37
|
+
this.url = redactUrl(response.url);
|
|
38
|
+
this.retryAfterMs = parseRetryAfter(response.headers.get('retry-after'));
|
|
39
|
+
this.body = body;
|
|
40
|
+
this.response = response;
|
|
41
|
+
}
|
|
42
|
+
static async fromResponse(response) {
|
|
43
|
+
const body = await readDiagnosticBody(response);
|
|
44
|
+
return new HttpError(response, body);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Executor-owned failure that always preserves the exact thrown value. */
|
|
48
|
+
export class FetchFailure extends Error {
|
|
49
|
+
classification;
|
|
50
|
+
cause;
|
|
51
|
+
constructor(cause, classification) {
|
|
52
|
+
super(cause instanceof Error ? cause.message : String(cause));
|
|
53
|
+
this.name = 'FetchFailure';
|
|
54
|
+
this.cause = cause;
|
|
55
|
+
this.classification = classification;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
export class BudgetExhausted extends Error {
|
|
59
|
+
constructor(message) {
|
|
60
|
+
super(message);
|
|
61
|
+
this.name = 'BudgetExhausted';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Cancellation is authoritative, then the common HTTP/network fallback is
|
|
66
|
+
* derived, then an optional provider classifier may enrich or override it.
|
|
67
|
+
*/
|
|
68
|
+
export function classifyFailure(cause, signal, classifier) {
|
|
69
|
+
if (signal.aborted)
|
|
70
|
+
return { kind: 'cancelled' };
|
|
71
|
+
const fallback = classifyCommon(cause);
|
|
72
|
+
return classifier?.(cause, fallback) ?? fallback;
|
|
73
|
+
}
|
|
74
|
+
function classifyCommon(cause) {
|
|
75
|
+
if (cause instanceof HttpError) {
|
|
76
|
+
if (cause.status === 429)
|
|
77
|
+
return { kind: 'rate_limited', retryAfterMs: cause.retryAfterMs };
|
|
78
|
+
if (cause.status === 408 || cause.status === 425 || cause.status >= 500)
|
|
79
|
+
return { kind: 'transient' };
|
|
80
|
+
if (cause.status >= 400)
|
|
81
|
+
return { kind: 'permanent' };
|
|
82
|
+
return { kind: 'unknown' };
|
|
83
|
+
}
|
|
84
|
+
const code = errorCode(cause);
|
|
85
|
+
if (code !== undefined && TRANSIENT_NETWORK_CODES.has(code))
|
|
86
|
+
return { kind: 'transient' };
|
|
87
|
+
if (cause instanceof TypeError && /fetch failed|network|socket/i.test(cause.message))
|
|
88
|
+
return { kind: 'transient' };
|
|
89
|
+
// A request's own timeout/abort does not cancel the ingestion execution.
|
|
90
|
+
if (cause instanceof Error && (cause.name === 'TimeoutError' || cause.name === 'AbortError'))
|
|
91
|
+
return { kind: 'transient' };
|
|
92
|
+
return { kind: 'unknown' };
|
|
93
|
+
}
|
|
94
|
+
function errorCode(value) {
|
|
95
|
+
if (typeof value !== 'object' || value === null)
|
|
96
|
+
return undefined;
|
|
97
|
+
if ('code' in value && typeof value.code === 'string')
|
|
98
|
+
return value.code;
|
|
99
|
+
if ('cause' in value)
|
|
100
|
+
return errorCode(value.cause);
|
|
101
|
+
return undefined;
|
|
102
|
+
}
|
|
103
|
+
function parseRetryAfter(header) {
|
|
104
|
+
if (header === null)
|
|
105
|
+
return undefined;
|
|
106
|
+
const seconds = Number(header);
|
|
107
|
+
if (Number.isFinite(seconds))
|
|
108
|
+
return Math.max(0, seconds * 1000);
|
|
109
|
+
const date = Date.parse(header);
|
|
110
|
+
if (Number.isNaN(date))
|
|
111
|
+
return undefined;
|
|
112
|
+
return Math.max(0, date - Date.now());
|
|
113
|
+
}
|
|
114
|
+
async function readDiagnosticBody(response) {
|
|
115
|
+
try {
|
|
116
|
+
const text = await response.clone().text();
|
|
117
|
+
return text.length > DIAGNOSTIC_BODY_LIMIT ? `${text.slice(0, DIAGNOSTIC_BODY_LIMIT)}…` : text;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return '';
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
// Query strings may carry API keys; keep only origin and path in diagnostics.
|
|
124
|
+
function redactUrl(url) {
|
|
125
|
+
try {
|
|
126
|
+
const parsed = new URL(url);
|
|
127
|
+
return `${parsed.origin}${parsed.pathname}`;
|
|
128
|
+
}
|
|
129
|
+
catch {
|
|
130
|
+
return url;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SelectedStream } from './registry.js';
|
|
2
|
+
import type { DestinationAdapter, ExecutionResult, Journal } from './types.js';
|
|
3
|
+
export interface BackfillRequest {
|
|
4
|
+
/** Stable identity: resuming the same backfill reuses its isolated checkpoint namespace. */
|
|
5
|
+
id: string;
|
|
6
|
+
from: Date | undefined;
|
|
7
|
+
to: Date | undefined;
|
|
8
|
+
}
|
|
9
|
+
export interface ExecutionRequest {
|
|
10
|
+
selected: readonly SelectedStream[];
|
|
11
|
+
backfill: BackfillRequest | undefined;
|
|
12
|
+
}
|
|
13
|
+
export interface ExecutionEnv {
|
|
14
|
+
journal: Journal;
|
|
15
|
+
destination: DestinationAdapter;
|
|
16
|
+
signal?: AbortSignal;
|
|
17
|
+
/** Host-default execution budget. Exhaustion preserves committed progress. */
|
|
18
|
+
maxDurationMs?: number;
|
|
19
|
+
/** Finite number of mapped batches buffered between fetch and load. */
|
|
20
|
+
prefetchBatches?: number;
|
|
21
|
+
now?: () => Date;
|
|
22
|
+
log?: (message: string) => void;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* One transient execution context for one exact stream selection. The run id
|
|
26
|
+
* correlates journal and telemetry evidence only: every stream plans and
|
|
27
|
+
* recovers independently from its own journal-backed checkpoint.
|
|
28
|
+
*/
|
|
29
|
+
export declare function runIngestion(request: ExecutionRequest, input: ExecutionEnv): Promise<ExecutionResult>;
|
|
30
|
+
//# sourceMappingURL=executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.d.ts","sourceRoot":"","sources":["../src/executor.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAA;AAEnD,OAAO,KAAK,EAIV,kBAAkB,EAClB,eAAe,EACf,OAAO,EAOR,MAAM,YAAY,CAAA;AAiBnB,MAAM,WAAW,eAAe;IAC9B,4FAA4F;IAC5F,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,IAAI,GAAG,SAAS,CAAA;IACtB,EAAE,EAAE,IAAI,GAAG,SAAS,CAAA;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,SAAS,cAAc,EAAE,CAAA;IACnC,QAAQ,EAAE,eAAe,GAAG,SAAS,CAAA;CACtC;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,CAAA;IAChB,WAAW,EAAE,kBAAkB,CAAA;IAC/B,MAAM,CAAC,EAAE,WAAW,CAAA;IACpB,8EAA8E;IAC9E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,uEAAuE;IACvE,eAAe,CAAC,EAAE,MAAM,CAAA;IACxB,GAAG,CAAC,EAAE,MAAM,IAAI,CAAA;IAChB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;CAChC;AA0CD;;;;GAIG;AACH,wBAAsB,YAAY,CAAC,OAAO,EAAE,gBAAgB,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,eAAe,CAAC,CAsC3G"}
|