@eltjs/elt 0.1.3 → 0.2.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 +1 -1
- package/dist/core/derived-dataset.d.ts +5 -0
- package/dist/core/derived-dataset.js +14 -0
- package/dist/core/derived-refreshes.d.ts +2 -2
- package/dist/core/derived-refreshes.js +16 -7
- package/dist/core/pipeline.js +4 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1058,7 +1058,7 @@ Two rules exist:
|
|
|
1058
1058
|
## When a derived dataset fails
|
|
1059
1059
|
|
|
1060
1060
|
- **A rule failure** is the output of its input: the status `failed` and the reason. The load and the run continue, and the next refresh tries the input again. See [ADR 0004](./docs/adr/0004-a-rule-failure-stays-on-its-output.md).
|
|
1061
|
-
- **A dataset failure**, for example SQL that SQLite refuses, rejects the run
|
|
1061
|
+
- **A dataset failure**, for example SQL that SQLite refuses, rejects the run, or it stops the watch, with a `DerivedDatasetError`. Its `dataset` names the dataset, and its `cause` is the error of the store. The passes that a stopped watch ended are `cancelled` in the sync history, and their records do not name the failure.
|
|
1062
1062
|
- **A copy that failed** keeps its last committed rows. The derived data of those rows stays as it was.
|
|
1063
1063
|
|
|
1064
1064
|
## The same concepts in other systems
|
|
@@ -7,6 +7,11 @@ export type RefreshTrigger = {
|
|
|
7
7
|
readonly kind: 'pass';
|
|
8
8
|
readonly tried: ReadonlySet<string>;
|
|
9
9
|
};
|
|
10
|
+
export declare class DerivedDatasetError extends Error {
|
|
11
|
+
name: string;
|
|
12
|
+
readonly dataset: string;
|
|
13
|
+
constructor(dataset: string, cause: unknown);
|
|
14
|
+
}
|
|
10
15
|
export declare abstract class DerivedDataset<Target extends DestinationTarget> {
|
|
11
16
|
abstract readonly name: string;
|
|
12
17
|
validate(_connections: readonly Connection<Target>[]): void;
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
import { errorMessage } from '../state/sync-history.js';
|
|
2
|
+
// A dataset's own failure, such as SQL its store refuses, named for whoever
|
|
3
|
+
// reads it in a log: the cause alone, such as "no such column: x", does not say
|
|
4
|
+
// which dataset of the pipeline failed.
|
|
5
|
+
export class DerivedDatasetError extends Error {
|
|
6
|
+
name = 'DerivedDatasetError';
|
|
7
|
+
dataset;
|
|
8
|
+
constructor(dataset, cause) {
|
|
9
|
+
super(`Derived dataset ${dataset} failed: ${errorMessage(cause)}`, {
|
|
10
|
+
cause,
|
|
11
|
+
});
|
|
12
|
+
this.dataset = dataset;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
1
15
|
// Data a pipeline keeps in step with what its copies load, and can always
|
|
2
16
|
// rebuild from it (Kleppmann's derived data; Estuary's derivations), such as a
|
|
3
17
|
// view over loaded tables or the text of the files a copy stored. Each kind
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { Connection } from './connection.ts';
|
|
2
|
-
import type
|
|
2
|
+
import { type DerivedDataset, type RefreshTrigger } from './derived-dataset.ts';
|
|
3
3
|
import type { Target as DestinationTarget } from './target.ts';
|
|
4
4
|
type Kind = RefreshTrigger['kind'];
|
|
5
5
|
export declare class DerivedRefreshes<Target extends DestinationTarget> {
|
|
6
6
|
#private;
|
|
7
|
-
constructor(datasets: readonly DerivedDataset<Target>[], connections: readonly Connection<Target>[], signal: AbortSignal | undefined, stop: (
|
|
7
|
+
constructor(datasets: readonly DerivedDataset<Target>[], connections: readonly Connection<Target>[], signal: AbortSignal | undefined, stop: () => void);
|
|
8
8
|
get failure(): {
|
|
9
9
|
readonly error: unknown;
|
|
10
10
|
} | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { Latch } from '@zukhruf/async';
|
|
2
|
+
import { DerivedDatasetError, } from './derived-dataset.js';
|
|
2
3
|
// No refresh runs.
|
|
3
4
|
class Idle {
|
|
4
5
|
}
|
|
@@ -84,12 +85,20 @@ export class DerivedRefreshes {
|
|
|
84
85
|
for (const [index, dataset] of this.#datasets.entries()) {
|
|
85
86
|
const tried = this.#tried.get(dataset) ?? new Set();
|
|
86
87
|
this.#tried.set(dataset, tried);
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
88
|
+
try {
|
|
89
|
+
await dataset.refresh({
|
|
90
|
+
connections: this.#connections,
|
|
91
|
+
earlier: this.#datasets.slice(0, index),
|
|
92
|
+
signal: this.#signal,
|
|
93
|
+
trigger: { kind, tried },
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
// A stop is not the dataset's failure.
|
|
98
|
+
if (this.#signal?.aborted === true)
|
|
99
|
+
throw error;
|
|
100
|
+
throw new DerivedDatasetError(dataset.name, error);
|
|
101
|
+
}
|
|
93
102
|
}
|
|
94
103
|
// The passes it follows ended, and their memory with them.
|
|
95
104
|
if (kind === 'pass')
|
|
@@ -109,7 +118,7 @@ export class DerivedRefreshes {
|
|
|
109
118
|
catch (error) {
|
|
110
119
|
if (this.#signal?.aborted !== true) {
|
|
111
120
|
this.#health = new Failed(error);
|
|
112
|
-
this.#stop(
|
|
121
|
+
this.#stop();
|
|
113
122
|
}
|
|
114
123
|
}
|
|
115
124
|
finally {
|
package/dist/core/pipeline.js
CHANGED
|
@@ -128,7 +128,10 @@ export class Pipeline {
|
|
|
128
128
|
const controller = new AbortController();
|
|
129
129
|
const watching = AbortSignal.any([signal, controller.signal]);
|
|
130
130
|
const stopped = [];
|
|
131
|
-
const refreshes = new DerivedRefreshes(this.derived, this.connections, watching,
|
|
131
|
+
const refreshes = new DerivedRefreshes(this.derived, this.connections, watching,
|
|
132
|
+
// With no reason: the passes it stops were not refused by this failure,
|
|
133
|
+
// and no sync history records a dataset's failure (ADR 0004).
|
|
134
|
+
() => controller.abort());
|
|
132
135
|
const passes = interleave(this.connections
|
|
133
136
|
.filter(({ steps }) => steps.length > 0)
|
|
134
137
|
.map((connection) => this.#watch(connection, watching, controller, stopped, () => refreshes.request('commit'))), Number.POSITIVE_INFINITY);
|
package/dist/index.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ export { Copy, type CopyOutcome, type CopyResult } from './core/copy.ts';
|
|
|
5
5
|
export { CopyConfiguration, type DedupPolicy, } from './core/copy-configuration.ts';
|
|
6
6
|
export type { Deduplication } from './core/deduplication.ts';
|
|
7
7
|
export { Destination, type DestinationSyncMode, type Load, } from './core/destination.ts';
|
|
8
|
-
export { DerivedDataset, type RefreshTrigger } from './core/derived-dataset.ts';
|
|
8
|
+
export { DerivedDataset, DerivedDatasetError, type RefreshTrigger, } from './core/derived-dataset.ts';
|
|
9
9
|
export { DocumentParser } from './core/document-parser.ts';
|
|
10
10
|
export { DocumentText } from './core/document-text.ts';
|
|
11
11
|
export { type LoadedRow, Rule, type RuleColumn, type RuleResult, } from './core/rule.ts';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ export { Connection } from './core/connection.js';
|
|
|
3
3
|
export { Copy } from './core/copy.js';
|
|
4
4
|
export { CopyConfiguration, } from './core/copy-configuration.js';
|
|
5
5
|
export { Destination, } from './core/destination.js';
|
|
6
|
-
export { DerivedDataset } from './core/derived-dataset.js';
|
|
6
|
+
export { DerivedDataset, DerivedDatasetError, } from './core/derived-dataset.js';
|
|
7
7
|
export { DocumentParser } from './core/document-parser.js';
|
|
8
8
|
export { DocumentText } from './core/document-text.js';
|
|
9
9
|
export { Rule, } from './core/rule.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eltjs/elt",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A pipeline that copies the data of apps and services into stores that SQL can read: sources, streams, checkpoints, watches and derived datasets.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|