@xeplr/actions 2.2.5 → 2.2.6

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
@@ -569,6 +569,8 @@ On Postgres, MySQL and SQL Server the staging table is shaped like the target (`
569
569
 
570
570
  Beside the tables it loads, the uploader makes three of its own: `<table>_import_errors` (refused rows), `<table>__xeplr_staging` (a staged load in progress) and, in a DuckDB file, `__xeplr_loads` (its ledger). `isInternalTable(name)` (also accepts a listing row `{ name }`) says whether a table is one of these, so an app listing a database's tables for people to pick from can leave them out. They still exist and are read where they belong.
571
571
 
572
+ It also adds its own columns to every table it loads: `__xeplr_id__` (Postgres, MySQL, SQL Server) and `__xeplr_movement_id__` (every target), the row's id and the load that wrote it. `isInternalColumn(name)` (or `{ name }`) says whether a column is one of these. **They are never carried into another table:** when a loaded table is loaded somewhere else (an import copied into a cube's warehouse, a move between databases), the uploader drops them from the target's columns, whether inferred from the rows or declared by the caller, and the new load stamps its own. Carried along, they clashed with the target's own column (`Column with name __xeplr_movement_id__ already exists`) and would have marked rows with a load that never wrote them.
573
+
572
574
  ### A written DuckDB file keeps no index
573
575
 
574
576
  DuckDB 1.5.4–1.5.6 has a bug ([duckdb/duckdb#26106](https://github.com/duckdb/duckdb/issues/26106), open upstream): when a process dies with rows still only in the WAL, the next open replays them into the table and the next checkpoint drops them from every index on it. The first `DELETE` that reaches one fails with *"Failed to delete all rows from index"*, which is **FATAL**: the whole file is unusable until the process restarts. Any index, unique or not. A table with no index is unaffected.
package/index.js CHANGED
@@ -48,6 +48,7 @@ module.exports = {
48
48
  uploader: uploader,
49
49
  // The uploader's own tables (_import_errors, __xeplr_staging, __xeplr_loads), to leave out of table pickers.
50
50
  isInternalTable: require('./lib/drivers/db/internalTables').isInternalTable,
51
+ isInternalColumn: require('./lib/drivers/db/internalTables').isInternalColumn,
51
52
  inferColumns: uploader.inferColumns,
52
53
  streaming: streaming,
53
54
  drivers: { db: dbDrivers, file: fileDrivers },
@@ -13,6 +13,13 @@ var ERRORS_SUFFIX = '_import_errors';
13
13
  var STAGING_SUFFIX = require('./staging').SUFFIX;
14
14
  var LOADS_LEDGER = '__xeplr_loads';
15
15
 
16
+ // THE COLUMNS IT ADDS TO EVERY TABLE IT LOADS: a row's own id and the
17
+ // movement that wrote it. They describe a load into THAT table, so they are
18
+ // never carried into another one — loading a loaded table elsewhere (an
19
+ // import copied into a cube's warehouse, a move between databases) leaves
20
+ // them behind and the new load stamps its own (uploader/index.js).
21
+ var INTERNAL_COLUMNS = ['__xeplr_id__', '__xeplr_movement_id__'];
22
+
16
23
  function endsWith(s, suffix) {
17
24
  return s.length > suffix.length && s.slice(-suffix.length) === suffix;
18
25
  }
@@ -23,8 +30,16 @@ function isInternalTable(name) {
23
30
  return endsWith(s, ERRORS_SUFFIX) || endsWith(s, STAGING_SUFFIX) || s === LOADS_LEDGER;
24
31
  }
25
32
 
33
+ /** Whether a column is one the uploader adds itself. A name, or { name }. */
34
+ function isInternalColumn(column) {
35
+ var s = String(column == null ? '' : (typeof column === 'object' ? column.name || '' : column));
36
+ return INTERNAL_COLUMNS.indexOf(s) !== -1;
37
+ }
38
+
26
39
  module.exports = {
27
40
  isInternalTable: isInternalTable,
41
+ isInternalColumn: isInternalColumn,
42
+ INTERNAL_COLUMNS: INTERNAL_COLUMNS,
28
43
  ERRORS_SUFFIX: ERRORS_SUFFIX,
29
44
  STAGING_SUFFIX: STAGING_SUFFIX,
30
45
  LOADS_LEDGER: LOADS_LEDGER
@@ -59,6 +59,7 @@
59
59
  //
60
60
  // See project_xeplr_actions_streaming.md for the full contract.
61
61
 
62
+ var { isInternalColumn } = require('../drivers/db/internalTables');
62
63
  var path = require('path');
63
64
  var fsp = require('fs/promises');
64
65
 
@@ -149,6 +150,12 @@ async function upload(opts) {
149
150
  var scanLimit = Math.min(sample.length, opts.firstBatchScanRows);
150
151
  declared = inferColumns(sample.slice(0, scanLimit));
151
152
  }
153
+ // OUR OWN COLUMNS NEVER COME ALONG. A source that was itself loaded
154
+ // by the uploader (an import's table, a replica) has its own
155
+ // __xeplr_id__ and __xeplr_movement_id__; carried here they clashed
156
+ // with this table's ("already exists") and would have claimed rows
157
+ // for a load that never wrote them. This load stamps its own.
158
+ declared = declared.filter(function(c) { return !isInternalColumn(c); });
152
159
  columns = await runBootstrap(driver, pool, opts.targetTable, declared, opts.primaryKeys, opts.staged);
153
160
  if (opts.staged) {
154
161
  opts.writeTable = await driver.prepareStaging(pool, opts.targetTable);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xeplr/actions",
3
- "version": "2.2.5",
3
+ "version": "2.2.6",
4
4
  "description": "Named-action registry with typed inputs and safe invocation — plus built-in actions for HTTP, DB, files, email, spawn",
5
5
  "main": "index.js",
6
6
  "scripts": {