@eltjs/elt 0.0.0-stage → 0.1.1
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/LICENSE +202 -0
- package/README.md +1153 -2
- package/dist/core/catalog.d.ts +7 -0
- package/dist/core/catalog.js +23 -0
- package/dist/core/change-signal.d.ts +9 -0
- package/dist/core/change-signal.js +4 -0
- package/dist/core/clock.d.ts +6 -0
- package/dist/core/clock.js +1 -0
- package/dist/core/connection.d.ts +20 -0
- package/dist/core/connection.js +31 -0
- package/dist/core/copy-configuration.d.ts +24 -0
- package/dist/core/copy-configuration.js +120 -0
- package/dist/core/copy.d.ts +27 -0
- package/dist/core/copy.js +56 -0
- package/dist/core/deduplication.d.ts +14 -0
- package/dist/core/deduplication.js +96 -0
- package/dist/core/derived-dataset.d.ts +19 -0
- package/dist/core/derived-dataset.js +10 -0
- package/dist/core/derived-refreshes.d.ts +15 -0
- package/dist/core/derived-refreshes.js +120 -0
- package/dist/core/destination.d.ts +22 -0
- package/dist/core/destination.js +17 -0
- package/dist/core/document-parser.d.ts +6 -0
- package/dist/core/document-parser.js +15 -0
- package/dist/core/document-text.d.ts +28 -0
- package/dist/core/document-text.js +72 -0
- package/dist/core/file-content.d.ts +5 -0
- package/dist/core/file-content.js +24 -0
- package/dist/core/file-extension.d.ts +1 -0
- package/dist/core/file-extension.js +9 -0
- package/dist/core/file-read.d.ts +20 -0
- package/dist/core/file-read.js +79 -0
- package/dist/core/file-storage.d.ts +8 -0
- package/dist/core/file-storage.js +4 -0
- package/dist/core/file-transfer.d.ts +12 -0
- package/dist/core/file-transfer.js +65 -0
- package/dist/core/formats/base64-format.d.ts +6 -0
- package/dist/core/formats/base64-format.js +13 -0
- package/dist/core/formats/calendar-date-format.d.ts +6 -0
- package/dist/core/formats/calendar-date-format.js +13 -0
- package/dist/core/formats/decimal-format.d.ts +9 -0
- package/dist/core/formats/decimal-format.js +41 -0
- package/dist/core/formats/declared-format.d.ts +12 -0
- package/dist/core/formats/declared-format.js +48 -0
- package/dist/core/formats/field-format.d.ts +6 -0
- package/dist/core/formats/field-format.js +10 -0
- package/dist/core/formats/int64-format.d.ts +6 -0
- package/dist/core/formats/int64-format.js +16 -0
- package/dist/core/formats/local-time-format.d.ts +6 -0
- package/dist/core/formats/local-time-format.js +10 -0
- package/dist/core/formats/local-timestamp-format.d.ts +6 -0
- package/dist/core/formats/local-timestamp-format.js +10 -0
- package/dist/core/formats/temporal-format.d.ts +7 -0
- package/dist/core/formats/temporal-format.js +23 -0
- package/dist/core/formats/timestamp-format.d.ts +7 -0
- package/dist/core/formats/timestamp-format.js +14 -0
- package/dist/core/identifiers.d.ts +10 -0
- package/dist/core/identifiers.js +51 -0
- package/dist/core/interleave.d.ts +1 -0
- package/dist/core/interleave.js +38 -0
- package/dist/core/partition.d.ts +16 -0
- package/dist/core/partition.js +60 -0
- package/dist/core/pass-channels.d.ts +46 -0
- package/dist/core/pass-channels.js +65 -0
- package/dist/core/pipeline.d.ts +37 -0
- package/dist/core/pipeline.js +546 -0
- package/dist/core/reader-relation.d.ts +6 -0
- package/dist/core/reader-relation.js +11 -0
- package/dist/core/record-validation.d.ts +58 -0
- package/dist/core/record-validation.js +128 -0
- package/dist/core/reload-mode.d.ts +9 -0
- package/dist/core/reload-mode.js +17 -0
- package/dist/core/replication.d.ts +8 -0
- package/dist/core/replication.js +478 -0
- package/dist/core/rule.d.ts +29 -0
- package/dist/core/rule.js +6 -0
- package/dist/core/signal-watch.d.ts +11 -0
- package/dist/core/signal-watch.js +110 -0
- package/dist/core/snapshot.d.ts +17 -0
- package/dist/core/snapshot.js +111 -0
- package/dist/core/source.d.ts +79 -0
- package/dist/core/source.js +227 -0
- package/dist/core/stream-change.d.ts +5 -0
- package/dist/core/stream-change.js +31 -0
- package/dist/core/stream.d.ts +26 -0
- package/dist/core/stream.js +86 -0
- package/dist/core/target-description.d.ts +15 -0
- package/dist/core/target-description.js +73 -0
- package/dist/core/target.d.ts +5 -0
- package/dist/core/target.js +14 -0
- package/dist/core/upkeep.d.ts +4 -0
- package/dist/core/upkeep.js +1 -0
- package/dist/core/writer.d.ts +41 -0
- package/dist/core/writer.js +17 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +36 -0
- package/dist/state/checkpoint-store.d.ts +29 -0
- package/dist/state/checkpoint-store.js +81 -0
- package/dist/state/sync-history-relations.d.ts +72 -0
- package/dist/state/sync-history-relations.js +74 -0
- package/dist/state/sync-history.d.ts +37 -0
- package/dist/state/sync-history.js +80 -0
- package/dist/storage/local-files.d.ts +15 -0
- package/dist/storage/local-files.js +121 -0
- package/package.json +48 -4
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { FieldFormat } from './field-format.js';
|
|
2
|
+
// A time spelling whose fraction of a second has exactly `precision` digits,
|
|
3
|
+
// none at precision 0, so the values of one field share a width and sort by
|
|
4
|
+
// bytes as they do in time. Precision defaults to milliseconds.
|
|
5
|
+
export class TemporalFormat extends FieldFormat {
|
|
6
|
+
precision;
|
|
7
|
+
constructor(precision = 3) {
|
|
8
|
+
super();
|
|
9
|
+
if (!Number.isInteger(precision) || precision < 0 || precision > 9)
|
|
10
|
+
throw new TypeError('Temporal precision must be an integer from 0 to 9 fraction digits');
|
|
11
|
+
this.precision = precision;
|
|
12
|
+
}
|
|
13
|
+
// The pattern of the seconds' fraction this precision requires.
|
|
14
|
+
get fraction() {
|
|
15
|
+
return this.precision === 0 ? '' : `\\.\\d{${this.precision}}`;
|
|
16
|
+
}
|
|
17
|
+
// Whether a `YYYY-MM-DDTHH:MM:SS` names a real second of the calendar.
|
|
18
|
+
real(second) {
|
|
19
|
+
const instant = `${second}.000Z`;
|
|
20
|
+
const time = Date.parse(instant);
|
|
21
|
+
return Number.isFinite(time) && new Date(time).toISOString() === instant;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { TemporalFormat } from './temporal-format.ts';
|
|
2
|
+
export declare class TimestampFormat extends TemporalFormat {
|
|
3
|
+
#private;
|
|
4
|
+
readonly name = "date-time";
|
|
5
|
+
accepts(value: string): boolean;
|
|
6
|
+
}
|
|
7
|
+
export declare function isTimestamp(value: unknown): value is string;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { TemporalFormat } from './temporal-format.js';
|
|
2
|
+
// format: 'date-time': a UTC instant ending in Z.
|
|
3
|
+
export class TimestampFormat extends TemporalFormat {
|
|
4
|
+
name = 'date-time';
|
|
5
|
+
#pattern = new RegExp(`^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}${this.fraction}Z$`);
|
|
6
|
+
accepts(value) {
|
|
7
|
+
return this.#pattern.test(value) && this.real(value.slice(0, 19));
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
const milliseconds = new TimestampFormat();
|
|
11
|
+
// A UTC timestamp exactly as Date#toISOString writes it.
|
|
12
|
+
export function isTimestamp(value) {
|
|
13
|
+
return typeof value === 'string' && milliseconds.accepts(value);
|
|
14
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare abstract class Identifiers {
|
|
2
|
+
#private;
|
|
3
|
+
protected abstract readonly maxBytes: number;
|
|
4
|
+
protected abstract readonly ownColumns: readonly string[];
|
|
5
|
+
abstract key(name: string): string;
|
|
6
|
+
columns<Column extends {
|
|
7
|
+
readonly field: string;
|
|
8
|
+
}>(columns: readonly Column[]): readonly (readonly [Column, string])[];
|
|
9
|
+
relation(name: string): string;
|
|
10
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
function hash(name) {
|
|
3
|
+
return createHash('sha256').update(name).digest('hex').slice(0, 8);
|
|
4
|
+
}
|
|
5
|
+
// Airbyte's rule: decomposed marks dropped, every run of whitespace and every
|
|
6
|
+
// other character outside [A-Za-z0-9_] replaced by `_`, case kept, and a
|
|
7
|
+
// leading digit prefixed with `_`. A name starting with `_elt_` drops its
|
|
8
|
+
// first `_`, since elt names what it creates for itself that way.
|
|
9
|
+
function sanitize(name) {
|
|
10
|
+
const clean = name
|
|
11
|
+
.normalize('NFKD')
|
|
12
|
+
.replace(/\p{M}/gu, '')
|
|
13
|
+
.replace(/\s+/gu, '_')
|
|
14
|
+
.replace(/[^A-Za-z0-9_]/gu, '_')
|
|
15
|
+
.replace(/^_(?=elt_)/i, '');
|
|
16
|
+
if (clean === '')
|
|
17
|
+
return '_';
|
|
18
|
+
return /^[0-9]/.test(clean) ? `_${clean}` : clean;
|
|
19
|
+
}
|
|
20
|
+
// How a destination names the columns, tables and views it creates from a
|
|
21
|
+
// stream, so that no name it cannot hold, or holds for itself, fails a load.
|
|
22
|
+
export class Identifiers {
|
|
23
|
+
// Each column with its name, in field order: a sanitized name another
|
|
24
|
+
// field already took is tried again with `_1`, `_2` and so on after the
|
|
25
|
+
// field.
|
|
26
|
+
columns(columns) {
|
|
27
|
+
const taken = new Set(this.ownColumns.map((name) => this.key(name)));
|
|
28
|
+
return columns.map((column) => {
|
|
29
|
+
const { field } = column;
|
|
30
|
+
let name = this.#fit(sanitize(field), field);
|
|
31
|
+
for (let suffix = 1; taken.has(this.key(name)); suffix += 1)
|
|
32
|
+
name = this.#fit(sanitize(`${field}_${suffix}`), `${field}_${suffix}`);
|
|
33
|
+
taken.add(this.key(name));
|
|
34
|
+
return [column, name];
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
// A table or view sees no other name to collide with, so one that
|
|
38
|
+
// sanitizing changed keeps a hash of what it was asked for.
|
|
39
|
+
relation(name) {
|
|
40
|
+
const clean = sanitize(name);
|
|
41
|
+
return clean === name
|
|
42
|
+
? this.#fit(name, name)
|
|
43
|
+
: this.#fit(`${clean}_${hash(name)}`, name);
|
|
44
|
+
}
|
|
45
|
+
// A sanitized name is ASCII, so cutting it by bytes keeps whole characters.
|
|
46
|
+
#fit(name, original) {
|
|
47
|
+
if (Buffer.byteLength(name) <= this.maxBytes)
|
|
48
|
+
return name;
|
|
49
|
+
return `${name.slice(0, this.maxBytes - 9)}_${hash(original)}`;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function interleave<T>(generators: readonly AsyncGenerator<T>[], concurrency: number): AsyncGenerator<T>;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// Up to concurrency generators run together, each value yielded as it
|
|
2
|
+
// arrives. A generator is asked for its next value only after the consumer
|
|
3
|
+
// took its last one, so whatever that value refers to stays valid until the
|
|
4
|
+
// consumer advances, whichever generator it came from.
|
|
5
|
+
export async function* interleave(generators, concurrency) {
|
|
6
|
+
const waiting = [...generators];
|
|
7
|
+
const reading = new Map();
|
|
8
|
+
const next = (generator) => reading.set(generator, generator.next().then((result) => ({ generator, result })));
|
|
9
|
+
const start = () => {
|
|
10
|
+
const generator = waiting.shift();
|
|
11
|
+
if (generator !== undefined)
|
|
12
|
+
next(generator);
|
|
13
|
+
};
|
|
14
|
+
try {
|
|
15
|
+
while (reading.size < concurrency && waiting.length > 0)
|
|
16
|
+
start();
|
|
17
|
+
while (reading.size > 0) {
|
|
18
|
+
const { generator, result } = await Promise.race(reading.values());
|
|
19
|
+
if (result.done) {
|
|
20
|
+
reading.delete(generator);
|
|
21
|
+
start();
|
|
22
|
+
continue;
|
|
23
|
+
}
|
|
24
|
+
// Keep the yielding generator tracked so an early return closes it too.
|
|
25
|
+
yield result.value;
|
|
26
|
+
reading.delete(generator);
|
|
27
|
+
next(generator);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
for (const [generator, pending] of reading) {
|
|
32
|
+
await pending.catch(() => undefined);
|
|
33
|
+
await generator.return(undefined);
|
|
34
|
+
}
|
|
35
|
+
for (const generator of waiting)
|
|
36
|
+
await generator.return(undefined);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { Deduplication } from './deduplication.ts';
|
|
2
|
+
import type { KeyValue } from './source.ts';
|
|
3
|
+
import type { Stream } from './stream.ts';
|
|
4
|
+
export type Partition = Readonly<Record<string, KeyValue>>;
|
|
5
|
+
export type PartitionState = {
|
|
6
|
+
readonly partition: Partition;
|
|
7
|
+
readonly state: unknown;
|
|
8
|
+
};
|
|
9
|
+
export declare function partitionIdentity(stream: Stream): Deduplication;
|
|
10
|
+
export declare function assertPartitions(stream: Stream, partitions: readonly Partition[]): void;
|
|
11
|
+
export declare function readPartition(identity: Deduplication, value: unknown): {
|
|
12
|
+
readonly key: string;
|
|
13
|
+
readonly partition: Partition;
|
|
14
|
+
};
|
|
15
|
+
export declare function assertInPartition(stream: Stream, partition: Partition, value: unknown): void;
|
|
16
|
+
export declare function readPartitionStates(stream: Stream, state: unknown): Map<string, PartitionState>;
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { Deduplication } from './deduplication.js';
|
|
2
|
+
// The partitionKey fields reuse deduplication's scalar key rules and encoding.
|
|
3
|
+
export function partitionIdentity(stream) {
|
|
4
|
+
if (stream.partitionKey === undefined)
|
|
5
|
+
throw new TypeError(`Stream ${stream.name} is not partitioned`);
|
|
6
|
+
return new Deduplication(stream, stream.partitionKey);
|
|
7
|
+
}
|
|
8
|
+
export function assertPartitions(stream, partitions) {
|
|
9
|
+
const identity = partitionIdentity(stream);
|
|
10
|
+
// A stream with no partitions reads nothing, as an Airbyte partition
|
|
11
|
+
// router with no values gives no slices.
|
|
12
|
+
if (!Array.isArray(partitions))
|
|
13
|
+
throw new TypeError(`Stream ${stream.name} lists no partitions`);
|
|
14
|
+
const keys = new Set();
|
|
15
|
+
for (const partition of partitions) {
|
|
16
|
+
const { key } = readPartition(identity, partition);
|
|
17
|
+
if (keys.has(key))
|
|
18
|
+
throw new TypeError(`Stream ${stream.name} lists partition ${key} twice`);
|
|
19
|
+
keys.add(key);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
// Exactly the partitionKey fields, each a valid non-null scalar.
|
|
23
|
+
export function readPartition(identity, value) {
|
|
24
|
+
if (value === null ||
|
|
25
|
+
typeof value !== 'object' ||
|
|
26
|
+
Object.keys(value).length !== identity.primaryKey.length)
|
|
27
|
+
throw new TypeError(`Stream ${identity.stream.name} partitions carry exactly ${JSON.stringify(identity.primaryKey)}`);
|
|
28
|
+
return {
|
|
29
|
+
key: identity.key(value),
|
|
30
|
+
partition: Object.freeze(Object.fromEntries(identity.primaryKey.map((field) => [
|
|
31
|
+
field,
|
|
32
|
+
identity.value(value, field),
|
|
33
|
+
]))),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
// Every record and deletion names the partition it was read for, so a
|
|
37
|
+
// connector cannot load rows without, or under the wrong, partition value.
|
|
38
|
+
export function assertInPartition(stream, partition, value) {
|
|
39
|
+
for (const [field, expected] of Object.entries(partition)) {
|
|
40
|
+
const actual = value !== null && typeof value === 'object'
|
|
41
|
+
? Reflect.get(value, field)
|
|
42
|
+
: undefined;
|
|
43
|
+
if (actual !== expected)
|
|
44
|
+
throw new TypeError(`Stream ${stream.name} record for partition ${JSON.stringify(partition)} carries ${field} ${JSON.stringify(actual)}`);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
// The checkpoint of a partitioned stream, as Source.read wrote it: each
|
|
48
|
+
// partition's own source state.
|
|
49
|
+
export function readPartitionStates(stream, state) {
|
|
50
|
+
const identity = partitionIdentity(stream);
|
|
51
|
+
const partitions = Reflect.get(Object(state), 'partitions');
|
|
52
|
+
if (partitions === undefined)
|
|
53
|
+
return new Map();
|
|
54
|
+
if (!Array.isArray(partitions))
|
|
55
|
+
throw new TypeError(`Stream ${stream.name} checkpoint holds partitions that are not a list`);
|
|
56
|
+
return new Map(partitions.map((entry) => {
|
|
57
|
+
const { key, partition } = readPartition(identity, Reflect.get(Object(entry), 'partition'));
|
|
58
|
+
return [key, { partition, state: Reflect.get(Object(entry), 'state') }];
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { Connection } from './connection.ts';
|
|
2
|
+
import type { Copy, CopyOutcome } from './copy.ts';
|
|
3
|
+
import type { FailureType } from './source.ts';
|
|
4
|
+
import type { Target as DestinationTarget } from './target.ts';
|
|
5
|
+
import type { WriteCount } from './writer.ts';
|
|
6
|
+
export declare const passChannels: {
|
|
7
|
+
start: import("node:diagnostics_channel").Channel<any, any>;
|
|
8
|
+
progress: import("node:diagnostics_channel").Channel<any, any>;
|
|
9
|
+
finish: import("node:diagnostics_channel").Channel<any, any>;
|
|
10
|
+
failure: import("node:diagnostics_channel").Channel<any, any>;
|
|
11
|
+
};
|
|
12
|
+
/** A pass began: its sync history recorded the attempt. */
|
|
13
|
+
export declare class PassStarted {
|
|
14
|
+
readonly connection: Connection<DestinationTarget>;
|
|
15
|
+
constructor(connection: Connection<DestinationTarget>);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* One copy of a running pass so far, counted as Airbyte's sync stats tracker
|
|
19
|
+
* counts a stream: what the source emitted and what the destination
|
|
20
|
+
* committed. It ends once, complete or incomplete, as Airbyte's stream status
|
|
21
|
+
* does.
|
|
22
|
+
*/
|
|
23
|
+
export declare class CopyProgress {
|
|
24
|
+
readonly connection: Connection<DestinationTarget>;
|
|
25
|
+
readonly copy: Copy<DestinationTarget>;
|
|
26
|
+
readonly status: 'running' | 'complete' | 'incomplete';
|
|
27
|
+
readonly emitted: WriteCount;
|
|
28
|
+
readonly committed: WriteCount;
|
|
29
|
+
constructor(connection: Connection<DestinationTarget>, copy: Copy<DestinationTarget>, status: 'running' | 'complete' | 'incomplete', emitted: WriteCount, committed: WriteCount);
|
|
30
|
+
}
|
|
31
|
+
/** A pass ended, once its sync history recorded each copy's outcome. */
|
|
32
|
+
export declare class PassFinished {
|
|
33
|
+
readonly connection: Connection<DestinationTarget>;
|
|
34
|
+
readonly outcomes: readonly CopyOutcome<DestinationTarget>[];
|
|
35
|
+
constructor(connection: Connection<DestinationTarget>, outcomes: readonly CopyOutcome<DestinationTarget>[]);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* A connection produced no pass, such as an invalid connection or a failed
|
|
39
|
+
* change signal, once its sync history recorded why.
|
|
40
|
+
*/
|
|
41
|
+
export declare class PassFailed {
|
|
42
|
+
readonly connection: Connection<DestinationTarget>;
|
|
43
|
+
readonly error: unknown;
|
|
44
|
+
readonly failureType: FailureType;
|
|
45
|
+
constructor(connection: Connection<DestinationTarget>, error: unknown, failureType: FailureType);
|
|
46
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { channel } from 'node:diagnostics_channel';
|
|
2
|
+
// What each pass publishes while it runs, on node:diagnostics_channel
|
|
3
|
+
// channels, as Node asks a module to publish what it does
|
|
4
|
+
// (doc/api/diagnostics_channel.md) and as Got and undici publish their
|
|
5
|
+
// requests. A subscriber gets each message as unknown and tells it by its
|
|
6
|
+
// class. A subscriber that throws never stops the pass: Node reports the error
|
|
7
|
+
// as an uncaught exception. Messages carry no scope: a host that runs several
|
|
8
|
+
// passes tells its own by running them in an AsyncLocalStorage, as Node calls
|
|
9
|
+
// a subscriber in the async context of the publisher.
|
|
10
|
+
export const passChannels = {
|
|
11
|
+
start: channel('elt:pass.start'),
|
|
12
|
+
progress: channel('elt:copy.progress'),
|
|
13
|
+
finish: channel('elt:pass.finish'),
|
|
14
|
+
failure: channel('elt:pass.failure'),
|
|
15
|
+
};
|
|
16
|
+
/** A pass began: its sync history recorded the attempt. */
|
|
17
|
+
export class PassStarted {
|
|
18
|
+
connection;
|
|
19
|
+
constructor(connection) {
|
|
20
|
+
this.connection = connection;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* One copy of a running pass so far, counted as Airbyte's sync stats tracker
|
|
25
|
+
* counts a stream: what the source emitted and what the destination
|
|
26
|
+
* committed. It ends once, complete or incomplete, as Airbyte's stream status
|
|
27
|
+
* does.
|
|
28
|
+
*/
|
|
29
|
+
export class CopyProgress {
|
|
30
|
+
connection;
|
|
31
|
+
copy;
|
|
32
|
+
status;
|
|
33
|
+
emitted;
|
|
34
|
+
committed;
|
|
35
|
+
constructor(connection, copy, status, emitted, committed) {
|
|
36
|
+
this.connection = connection;
|
|
37
|
+
this.copy = copy;
|
|
38
|
+
this.status = status;
|
|
39
|
+
this.emitted = emitted;
|
|
40
|
+
this.committed = committed;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/** A pass ended, once its sync history recorded each copy's outcome. */
|
|
44
|
+
export class PassFinished {
|
|
45
|
+
connection;
|
|
46
|
+
outcomes;
|
|
47
|
+
constructor(connection, outcomes) {
|
|
48
|
+
this.connection = connection;
|
|
49
|
+
this.outcomes = outcomes;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* A connection produced no pass, such as an invalid connection or a failed
|
|
54
|
+
* change signal, once its sync history recorded why.
|
|
55
|
+
*/
|
|
56
|
+
export class PassFailed {
|
|
57
|
+
connection;
|
|
58
|
+
error;
|
|
59
|
+
failureType;
|
|
60
|
+
constructor(connection, error, failureType) {
|
|
61
|
+
this.connection = connection;
|
|
62
|
+
this.error = error;
|
|
63
|
+
this.failureType = failureType;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type SyncHistory } from '../state/sync-history.ts';
|
|
2
|
+
import type { PipelineClock } from './clock.ts';
|
|
3
|
+
import { Connection } from './connection.ts';
|
|
4
|
+
import { type Copy, type CopyOutcome, type CopyResult } from './copy.ts';
|
|
5
|
+
import type { DerivedDataset } from './derived-dataset.ts';
|
|
6
|
+
import type { Target as DestinationTarget } from './target.ts';
|
|
7
|
+
export type Pass<Target extends DestinationTarget> = {
|
|
8
|
+
readonly connection: Connection<Target>;
|
|
9
|
+
readonly outcomes: readonly CopyOutcome<Target>[];
|
|
10
|
+
};
|
|
11
|
+
export declare class PipelineError<Target extends DestinationTarget> extends AggregateError {
|
|
12
|
+
name: string;
|
|
13
|
+
readonly results: readonly CopyOutcome<Target>[];
|
|
14
|
+
constructor(results: readonly CopyOutcome<Target>[]);
|
|
15
|
+
}
|
|
16
|
+
export declare class WatchError extends AggregateError {
|
|
17
|
+
name: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class Pipeline<Target extends DestinationTarget> {
|
|
20
|
+
#private;
|
|
21
|
+
readonly connections: readonly Connection<Target>[];
|
|
22
|
+
readonly history: SyncHistory<Target>;
|
|
23
|
+
readonly derived: readonly DerivedDataset<Target>[];
|
|
24
|
+
constructor({ connections, history, derived, clock, }: {
|
|
25
|
+
connections: readonly Connection<Target>[];
|
|
26
|
+
history: SyncHistory<NoInfer<Target>>;
|
|
27
|
+
derived?: readonly DerivedDataset<NoInfer<Target>>[];
|
|
28
|
+
clock: PipelineClock;
|
|
29
|
+
});
|
|
30
|
+
clear(steps?: readonly Copy<Target>[]): Promise<void>;
|
|
31
|
+
run(options?: {
|
|
32
|
+
readonly signal?: AbortSignal;
|
|
33
|
+
}): Promise<CopyResult<Target>[]>;
|
|
34
|
+
watch({ signal, }: {
|
|
35
|
+
signal: AbortSignal;
|
|
36
|
+
}): AsyncGenerator<Pass<Target>>;
|
|
37
|
+
}
|