@eltjs/elt-sqlite 0.1.2 → 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 CHANGED
@@ -197,7 +197,8 @@ At each refresh, the computed dataset compares the inputs with the output rows.
197
197
  ## When something fails
198
198
 
199
199
  - **A rule failure** gives an output row with the status `failed` and the reason in `error`. The run continues, and the refresh after the next pass tries the input again.
200
- - **A dataset failure**, for example a query that SQLite refuses, rejects the run, or it stops the watch.
200
+ - **A dataset failure**, for example a query that SQLite refuses, rejects the run, or it stops the watch, with a `DerivedDatasetError` that names the dataset.
201
+ - **A table that is gone**, for example one that a person dropped by hand, is no failure. A computed dataset that reads it waits and keeps its output rows. When the copy loads the table again, the dataset asks the rule only for inputs that changed.
201
202
  - **A copy that failed** keeps its last committed rows. The derived datasets read those rows.
202
203
 
203
204
  ## Recipes
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { hash } from 'node:crypto';
2
2
  import { Declaration } from '@zukhruf/sqlite-drift';
3
3
  import { open } from '@zukhruf/sqlite-open';
4
4
  import { transaction } from '@zukhruf/sqlite-transaction';
@@ -78,6 +78,8 @@ export class ComputedDataset extends DerivedDataset {
78
78
  }
79
79
  async refresh({ connections, signal, trigger, }) {
80
80
  const inputs = this.#inputs(connections);
81
+ if (inputs === null)
82
+ return;
81
83
  using database = openDestination(this.#into.path);
82
84
  const key = this.#rule.key.name;
83
85
  const stored = transaction(database, () => {
@@ -119,9 +121,7 @@ export class ComputedDataset extends DerivedDataset {
119
121
  for (const [id, input] of inputs) {
120
122
  if (signal?.aborted)
121
123
  return;
122
- const inputHash = createHash('sha256')
123
- .update(JSON.stringify(input))
124
- .digest('hex');
124
+ const inputHash = hash('sha256', JSON.stringify(input));
125
125
  const rule = await this.#rule.identity(input);
126
126
  const before = stored.get(id);
127
127
  // A rule that does not handle the input keeps what it made before.
@@ -190,6 +190,10 @@ export class ComputedDataset extends DerivedDataset {
190
190
  // Each input by key, from the rows of each relation of from that exists
191
191
  // yet: what the copies loaded, or what a view before this dataset gives. A
192
192
  // key read twice keeps its first input.
193
+ // null while a relation reads a table that is gone, such as one a person
194
+ // dropped by hand: its copy loads it again, and the dataset waits with what it
195
+ // made, as a view waits. Read as no rows, it would delete every output, and the
196
+ // reload would pay the rule for each again.
193
197
  #inputs(connections) {
194
198
  const inputs = new Map();
195
199
  for (const target of this.#from) {
@@ -206,9 +210,16 @@ export class ComputedDataset extends DerivedDataset {
206
210
  .prepare('SELECT 1 FROM sqlite_schema WHERE lower("name") = lower(?)')
207
211
  .get(relation) === undefined)
208
212
  continue;
209
- for (const row of database
210
- .prepare(`SELECT * FROM ${quote(relation)}`)
211
- .all()) {
213
+ let rows;
214
+ try {
215
+ rows = database.prepare(`SELECT * FROM ${quote(relation)}`).all();
216
+ }
217
+ catch (error) {
218
+ if (Error.isError(error) && error.message.startsWith('no such table: '))
219
+ return null;
220
+ throw error;
221
+ }
222
+ for (const row of rows) {
212
223
  const read = this.#rule.read(row);
213
224
  if (read !== null && !inputs.has(read.key))
214
225
  inputs.set(read.key, read.input);
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { hash } from 'node:crypto';
2
2
  import { SQLiteColumns } from './sqlite-columns.js';
3
3
  import { SQLiteWriter, op, seq } from './sqlite-writer.js';
4
4
  export class SQLiteDeduplicatingWriter extends SQLiteWriter {
@@ -29,9 +29,7 @@ export class SQLiteDeduplicatingWriter extends SQLiteWriter {
29
29
  // Named after the table and its key, so a changed key builds a new index
30
30
  // and an unchanged one is never rebuilt.
31
31
  get #dedupIndex() {
32
- return `_elt_dedup_${createHash('sha256')
33
- .update(JSON.stringify([this.table.name, this.keys.map((key) => key.name)]))
34
- .digest('hex')}`;
32
+ return `_elt_dedup_${hash('sha256', JSON.stringify([this.table.name, this.keys.map((key) => key.name)]))}`;
35
33
  }
36
34
  // A stored table the load merges into must hold keys and cursors of the
37
35
  // stream's types, none null, and no key twice, which its index forbids once
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { hash } from 'node:crypto';
2
2
  import { identifiers } from './sqlite-identifiers.js';
3
3
  const quote = (name) => `"${name.replaceAll('"', '""')}"`;
4
4
  const tablePrefix = '_elt_files_';
@@ -26,10 +26,7 @@ export class SQLiteFileStore {
26
26
  // table a_b with column c and table a with column b_c would share chunks
27
27
  // and prune each other's.
28
28
  static tableName(table, column) {
29
- const key = createHash('sha256')
30
- .update(JSON.stringify([table.location, identifiers.key(column.name)]))
31
- .digest('hex')
32
- .slice(0, 40);
29
+ const key = hash('sha256', JSON.stringify([table.location, identifiers.key(column.name)])).slice(0, 40);
33
30
  return `${tablePrefix}${key}`;
34
31
  }
35
32
  #exists(location) {
@@ -1,4 +1,4 @@
1
- import { createHash } from 'node:crypto';
1
+ import { hash } from 'node:crypto';
2
2
  import { Declaration } from '@zukhruf/sqlite-drift';
3
3
  import { transaction } from '@zukhruf/sqlite-transaction';
4
4
  import { FileContent, TargetOwnedError, Writer, describeTarget, errorMessage, reloadMode, } from '@eltjs/elt';
@@ -71,7 +71,7 @@ export class SQLiteWriter extends Writer {
71
71
  database.exec(`DELETE FROM ${this.table.quotedName}`);
72
72
  }
73
73
  get hash() {
74
- return createHash('sha256').update(this.table.location).digest('hex');
74
+ return hash('sha256', this.table.location);
75
75
  }
76
76
  get fields() {
77
77
  return [
package/dist/view.js CHANGED
@@ -33,7 +33,7 @@ export class View extends DerivedDataset {
33
33
  database.prepare(this.#view.query);
34
34
  }
35
35
  catch (error) {
36
- const missing = error instanceof Error
36
+ const missing = Error.isError(error)
37
37
  ? /^no such table: (?:main\.)?(.+)$/.exec(error.message)?.[1]
38
38
  : undefined;
39
39
  const missingName = missing?.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eltjs/elt-sqlite",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "The SQLite destination of elt: each stream is a raw table in one SQLite file, with a reader view over it and a catalog of each table, view and column.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -39,7 +39,7 @@
39
39
  "node": ">=24"
40
40
  },
41
41
  "dependencies": {
42
- "@eltjs/elt": "^0.1.2",
42
+ "@eltjs/elt": "^0.2.0",
43
43
  "@zukhruf/mutex": "^0.8.10",
44
44
  "@zukhruf/sqlite-drift": "^0.8.10",
45
45
  "@zukhruf/sqlite-open": "^0.8.10",