@mostajs/orm-cli 0.5.5 → 0.5.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.
Files changed (2) hide show
  1. package/bin/mostajs.sh +113 -62
  2. package/package.json +1 -1
package/bin/mostajs.sh CHANGED
@@ -1962,41 +1962,53 @@ action_rep_add_replica() {
1962
1962
  else
1963
1963
  lag="0"
1964
1964
  fi
1965
- _replicator_run "
1966
- // Auto-register the project in ProjectManager if it's not already there —
1967
- // addReplica() requires the project to exist. We use the replica's own
1968
- // dialect/uri as the project config (it's typically the master).
1969
- const existing = typeof pm.listProjects === 'function' ? pm.listProjects() : [];
1970
- const known = Array.isArray(existing) && existing.some(p => (p.name ?? p) === '$project');
1971
- if (!known) {
1972
- const method = ['addProject','createProject','registerProject'].find(m => typeof pm[m] === 'function');
1973
- if (method) {
1974
- await pm[method]({ name: '$project', dialect: '$dialect', uri: '$uri', schemas: [] });
1975
- console.log(' project \\'$project\\' registered in ProjectManager');
1976
- }
1977
- }
1978
- await rm.addReplica('$project', {
1979
- name: '$name', role: '$role', dialect: '$dialect', uri: '$uri',
1980
- lagTolerance: $lag,
1981
- });
1982
- await save();
1965
+ # Direct tree-JSON patch — preserves URI verbatim (no masking), no DB
1966
+ # connection needed. Validation of the URI happens later when replicator
1967
+ # service or sync action actually touches the DB.
1968
+ _tree_patch "
1969
+ tree.replicas['$project'] = tree.replicas['$project'] || {};
1970
+ tree.replicas['$project']['$name'] = {
1971
+ role: '$role',
1972
+ dialect: '$dialect',
1973
+ uri: '$uri',
1974
+ pool: { min: 2, max: 20 },
1975
+ ...( '$role' === 'slave' ? { lagTolerance: $lag } : {} ),
1976
+ };
1983
1977
  console.log(' ✓ replica added : $name (' + '$role' + ') on project $project');
1984
1978
  "
1985
1979
  pause
1986
1980
  }
1987
1981
 
1982
+ # ----------------------------------------------------------------
1983
+ # Tree-file direct manipulation helpers
1984
+ # ----------------------------------------------------------------
1985
+ # The replicator's saveToFile() masks credentials (oracle://u:***@host)
1986
+ # which makes loadFromFile() unable to reconnect. For add/list/remove we
1987
+ # bypass the lib and patch the tree JSON directly, preserving the
1988
+ # original URI verbatim. The lib is still used for sync() / promote()
1989
+ # where it has logic beyond state tracking.
1990
+
1991
+ _tree_file() { _replicator_tree_file; }
1992
+
1993
+ _tree_read_json() {
1994
+ # Dump the tree JSON on stdout, empty-tree fallback if file missing.
1995
+ local tree_file
1996
+ tree_file=$(_tree_file)
1997
+ node -e "
1998
+ const fs = require('fs');
1999
+ const def = { replicas: {}, rules: {}, routing: {} };
2000
+ try { console.log(JSON.stringify(Object.assign(def, JSON.parse(fs.readFileSync('$tree_file','utf8'))))); }
2001
+ catch { console.log(JSON.stringify(def)); }
2002
+ "
2003
+ }
2004
+
1988
2005
  # List known projects from the replicator-tree.json — if any — and propose
1989
2006
  # the first one as default. Echoes the chosen project name to stdout.
1990
- # Returns 1 if user cancelled.
1991
2007
  _pick_project() {
1992
- local tree_file
1993
- tree_file=$(_replicator_tree_file)
1994
2008
  local projects=()
1995
- if [[ -f "$tree_file" ]]; then
1996
- while IFS= read -r p; do projects+=("$p"); done < <(
1997
- node -e "try{const t=JSON.parse(require('fs').readFileSync('$tree_file','utf8'));for(const k of Object.keys(t.replicas||{}))console.log(k)}catch{}" 2>/dev/null
1998
- )
1999
- fi
2009
+ while IFS= read -r p; do projects+=("$p"); done < <(
2010
+ _tree_read_json | node -e "let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{for(const k of Object.keys(JSON.parse(d).replicas||{}))console.log(k)})"
2011
+ )
2000
2012
  local default_project="fitzone"
2001
2013
  if [[ ${#projects[@]} -gt 0 ]]; then
2002
2014
  default_project="${projects[0]}"
@@ -2010,18 +2022,46 @@ _pick_project() {
2010
2022
  echo "$picked"
2011
2023
  }
2012
2024
 
2025
+ # Patch the tree JSON : exec a Node snippet with `tree` in scope.
2026
+ # After the snippet runs, the mutated tree is written back.
2027
+ _tree_patch() {
2028
+ local snippet="$1"
2029
+ local tree_file
2030
+ tree_file=$(_tree_file)
2031
+ mkdir -p "$(dirname "$tree_file")"
2032
+ TREE_FILE="$tree_file" node --input-type=module -e "
2033
+ const { readFileSync, writeFileSync, existsSync } = await import('fs');
2034
+ const { mkdirSync } = await import('fs');
2035
+ const def = { replicas: {}, rules: {}, routing: {} };
2036
+ let tree = def;
2037
+ if (existsSync(process.env.TREE_FILE)) {
2038
+ try { tree = Object.assign(def, JSON.parse(readFileSync(process.env.TREE_FILE, 'utf8'))); }
2039
+ catch { /* start fresh */ }
2040
+ }
2041
+ try { $snippet }
2042
+ catch (e) { console.error(' ✗ ' + e.message); process.exit(1); }
2043
+ writeFileSync(process.env.TREE_FILE, JSON.stringify(tree, null, 2) + '\n');
2044
+ "
2045
+ }
2046
+
2013
2047
  action_rep_list_replicas() {
2014
2048
  local project
2015
2049
  project=$(_pick_project)
2016
- _replicator_run "
2017
- const status = rm.getReplicaStatus('$project');
2018
- if (!status || status.length === 0) {
2019
- console.log(' (no replicas registered for project $project)');
2020
- } else {
2021
- for (const r of status) {
2022
- console.log(' ' + (r.role === 'master' ? '★' : '•') + ' ' + r.name + ' [' + r.role + '] lag=' + (r.lag ?? 'n/a') + 'ms');
2050
+ echo
2051
+ _tree_read_json | PROJECT="$project" node -e "
2052
+ let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{
2053
+ const tree = JSON.parse(d);
2054
+ const reps = tree.replicas[process.env.PROJECT] || {};
2055
+ const entries = Object.entries(reps);
2056
+ if (entries.length === 0) {
2057
+ console.log(' (no replicas registered for project ' + process.env.PROJECT + ')');
2058
+ return;
2023
2059
  }
2024
- }
2060
+ for (const [name, cfg] of entries) {
2061
+ const star = cfg.role === 'master' ? '\x1b[36m★\x1b[0m' : '•';
2062
+ console.log(' ' + star + ' ' + name + ' [' + cfg.role + '] ' + cfg.dialect + ' ' + (cfg.uri ?? '').replace(/:[^:@]+@/, ':***@'));
2063
+ }
2064
+ });
2025
2065
  "
2026
2066
  pause
2027
2067
  }
@@ -2044,10 +2084,14 @@ action_rep_remove_replica() {
2044
2084
  project=$(_pick_project)
2045
2085
  name=$(ask "Replica name" "slave-1")
2046
2086
  if ! confirm "Remove replica '$name' from project '$project'?"; then return; fi
2047
- _replicator_run "
2048
- await rm.removeReplica('$project', '$name');
2049
- await save();
2050
- console.log(' ✓ removed : $name');
2087
+ _tree_patch "
2088
+ if (!tree.replicas['$project'] || !tree.replicas['$project']['$name']) {
2089
+ console.log(' • not found : $project/$name');
2090
+ } else {
2091
+ delete tree.replicas['$project']['$name'];
2092
+ if (Object.keys(tree.replicas['$project']).length === 0) delete tree.replicas['$project'];
2093
+ console.log(' ✓ removed : $project/$name');
2094
+ }
2051
2095
  "
2052
2096
  pause
2053
2097
  }
@@ -2056,9 +2100,8 @@ action_rep_set_routing() {
2056
2100
  local project strategy
2057
2101
  project=$(_pick_project)
2058
2102
  strategy=$(ask "Strategy (round-robin | least-lag | random)" "least-lag")
2059
- _replicator_run "
2060
- rm.setReadRouting('$project', '$strategy');
2061
- await save();
2103
+ _tree_patch "
2104
+ tree.routing['$project'] = '$strategy';
2062
2105
  console.log(' ✓ read routing on $project = $strategy');
2063
2106
  "
2064
2107
  pause
@@ -2066,36 +2109,41 @@ action_rep_set_routing() {
2066
2109
 
2067
2110
  action_rep_add_rule() {
2068
2111
  local name source target mode colls conflict
2069
- name=$(ask "Rule name" "pg-to-mongo")
2070
- source=$(ask "Source project" "secuaccess")
2071
- target=$(ask "Target project" "analytics")
2112
+ # Pick source/target from known projects
2113
+ source=$(_pick_project)
2114
+ local src_default="$source"
2115
+ name=$(ask "Rule name (short, e.g. 'pg-to-mongo')" "cdc-${src_default}")
2116
+ target=$(ask "Target project" "$src_default")
2072
2117
  mode=$(ask "Mode (snapshot | cdc | bidirectional)" "cdc")
2073
2118
  colls=$(ask "Collections (comma-separated)" "users,clients")
2074
2119
  conflict=$(ask "Conflict resolution (source-wins | target-wins | timestamp)" "source-wins")
2075
- _replicator_run "
2076
- rm.addReplicationRule({
2077
- name: '$name', source: '$source', target: '$target', mode: '$mode',
2120
+ _tree_patch "
2121
+ tree.rules['$name'] = {
2122
+ source: '$source', target: '$target', mode: '$mode',
2078
2123
  collections: '$colls'.split(',').map(s => s.trim()).filter(Boolean),
2079
2124
  conflictResolution: '$conflict',
2080
2125
  enabled: true,
2081
- });
2082
- await save();
2126
+ };
2083
2127
  console.log(' ✓ rule added : $name ($source → $target, mode=$mode)');
2084
2128
  "
2085
2129
  pause
2086
2130
  }
2087
2131
 
2088
2132
  action_rep_list_rules() {
2089
- _replicator_run "
2090
- const rules = rm.listRules();
2091
- if (!rules || rules.length === 0) {
2092
- console.log(' (no CDC rules registered)');
2093
- } else {
2094
- for (const r of rules) {
2095
- const flag = r.enabled ? '✓' : '✗';
2096
- console.log(' ' + flag + ' ' + r.name + ' ' + r.source + ' → ' + r.target + ' [' + r.mode + '] ' + r.collections.join(','));
2133
+ echo
2134
+ _tree_read_json | node -e "
2135
+ let d='';process.stdin.on('data',c=>d+=c).on('end',()=>{
2136
+ const tree = JSON.parse(d);
2137
+ const rules = Object.entries(tree.rules || {});
2138
+ if (rules.length === 0) {
2139
+ console.log(' (no CDC rules registered)');
2140
+ return;
2097
2141
  }
2098
- }
2142
+ for (const [name, r] of rules) {
2143
+ const flag = r.enabled ? '\x1b[32m✓\x1b[0m' : '\x1b[31m✗\x1b[0m';
2144
+ console.log(' ' + flag + ' ' + name + ' ' + r.source + ' → ' + r.target + ' [' + r.mode + '] ' + (r.collections||[]).join(','));
2145
+ }
2146
+ });
2099
2147
  "
2100
2148
  pause
2101
2149
  }
@@ -2119,10 +2167,13 @@ action_rep_remove_rule() {
2119
2167
  local name
2120
2168
  name=$(ask "Rule name" "pg-to-mongo")
2121
2169
  if ! confirm "Remove CDC rule '$name'?"; then return; fi
2122
- _replicator_run "
2123
- rm.removeReplicationRule('$name');
2124
- await save();
2125
- console.log(' ✓ removed : $name');
2170
+ _tree_patch "
2171
+ if (!tree.rules['$name']) {
2172
+ console.log(' • not found : $name');
2173
+ } else {
2174
+ delete tree.rules['$name'];
2175
+ console.log(' ✓ removed : $name');
2176
+ }
2126
2177
  "
2127
2178
  pause
2128
2179
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mostajs/orm-cli",
3
- "version": "0.5.5",
3
+ "version": "0.5.6",
4
4
  "description": "Universal CLI to integrate @mostajs/orm into any project — one-shot `mostajs bootstrap` migrates a Prisma project (codemod + deps + schema convert + DDL) to 13 databases with zero code change.",
5
5
  "author": "Dr Hamid MADANI <drmdh@msn.com>",
6
6
  "license": "AGPL-3.0-or-later",