@mostajs/orm-cli 0.1.0 → 0.2.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/bin/mostajs.sh +612 -109
- package/package.json +6 -2
package/bin/mostajs.sh
CHANGED
|
@@ -53,6 +53,110 @@ warn() { echo -e " ${YELLOW}⚠${RESET} $*"; }
|
|
|
53
53
|
err() { echo -e " ${RED}✗${RESET} $*"; }
|
|
54
54
|
dim() { echo -e " ${DIM}$*${RESET}"; }
|
|
55
55
|
|
|
56
|
+
# ============================================================
|
|
57
|
+
# ERROR HANDLING + AUTO-INSTALL
|
|
58
|
+
# ============================================================
|
|
59
|
+
|
|
60
|
+
# Run a node command with clean error reporting.
|
|
61
|
+
# Usage: run_node <script_path> [env=val ...]
|
|
62
|
+
run_node() {
|
|
63
|
+
local script="$1"; shift
|
|
64
|
+
local envvars=("$@")
|
|
65
|
+
local rc=0
|
|
66
|
+
if [[ ${#envvars[@]} -gt 0 ]]; then
|
|
67
|
+
env "${envvars[@]}" node "$script" 2>&1
|
|
68
|
+
else
|
|
69
|
+
node "$script" 2>&1
|
|
70
|
+
fi
|
|
71
|
+
rc=${PIPESTATUS[0]}
|
|
72
|
+
if [[ $rc -ne 0 ]]; then
|
|
73
|
+
err "Script failed (exit $rc)"
|
|
74
|
+
return $rc
|
|
75
|
+
fi
|
|
76
|
+
return 0
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Check if a package is installed locally; if not, offer to install it.
|
|
80
|
+
# Usage: ensure_pkg <package-name> [<additional-pkg> ...]
|
|
81
|
+
ensure_pkg() {
|
|
82
|
+
local pkgs=("$@")
|
|
83
|
+
local missing=()
|
|
84
|
+
for pkg in "${pkgs[@]}"; do
|
|
85
|
+
# Try to resolve via node's require resolution (works for any layout)
|
|
86
|
+
if ! node -e "require.resolve('$pkg')" >/dev/null 2>&1; then
|
|
87
|
+
# Also check if there's a node_modules with it
|
|
88
|
+
if [[ ! -d "$PROJECT_ROOT/node_modules/$pkg" ]]; then
|
|
89
|
+
missing+=("$pkg")
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
if [[ ${#missing[@]} -eq 0 ]]; then
|
|
95
|
+
return 0
|
|
96
|
+
fi
|
|
97
|
+
|
|
98
|
+
warn "Missing package(s): ${missing[*]}"
|
|
99
|
+
if confirm "Install now with $PKG_MANAGER?"; then
|
|
100
|
+
cd "$PROJECT_ROOT" || return 1
|
|
101
|
+
case "$PKG_MANAGER" in
|
|
102
|
+
pnpm) pnpm add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
103
|
+
yarn) yarn add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
104
|
+
bun) bun add "${missing[@]}" 2>&1 | tail -5 ;;
|
|
105
|
+
*) npm install --save "${missing[@]}" --legacy-peer-deps 2>&1 | tail -5 ;;
|
|
106
|
+
esac
|
|
107
|
+
local rc=${PIPESTATUS[0]}
|
|
108
|
+
if [[ $rc -ne 0 ]]; then
|
|
109
|
+
err "Install failed"
|
|
110
|
+
return $rc
|
|
111
|
+
fi
|
|
112
|
+
ok "Installed"
|
|
113
|
+
return 0
|
|
114
|
+
else
|
|
115
|
+
err "Cannot proceed without: ${missing[*]}"
|
|
116
|
+
return 1
|
|
117
|
+
fi
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
# Resolve path to a specific installed package module file
|
|
121
|
+
# Usage: resolve_pkg <package>/dist/index.js
|
|
122
|
+
# Writes path to stdout; returns 0 on success, 1 on failure
|
|
123
|
+
resolve_pkg_path() {
|
|
124
|
+
local pkg="$1"
|
|
125
|
+
local result
|
|
126
|
+
result=$(node -e "
|
|
127
|
+
try {
|
|
128
|
+
const p = require.resolve('$pkg', { paths: [process.cwd(), '$PROJECT_ROOT'] });
|
|
129
|
+
console.log(p);
|
|
130
|
+
} catch (e) {
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
" 2>/dev/null) || return 1
|
|
134
|
+
echo "$result"
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
# Check if a driver is needed for the given dialect, and install it if missing.
|
|
138
|
+
# Usage: ensure_dialect_driver <dialect>
|
|
139
|
+
ensure_dialect_driver() {
|
|
140
|
+
local dialect="$1"
|
|
141
|
+
local driver=""
|
|
142
|
+
case "$dialect" in
|
|
143
|
+
sqlite) driver="better-sqlite3" ;;
|
|
144
|
+
postgres|cockroachdb) driver="pg" ;;
|
|
145
|
+
mysql) driver="mysql2" ;;
|
|
146
|
+
mariadb) driver="mariadb" ;;
|
|
147
|
+
mssql) driver="mssql" ;;
|
|
148
|
+
oracle) driver="oracledb" ;;
|
|
149
|
+
db2) driver="ibm_db" ;;
|
|
150
|
+
hana) driver="@sap/hana-client" ;;
|
|
151
|
+
spanner) driver="@google-cloud/spanner" ;;
|
|
152
|
+
sybase) driver="sybase" ;;
|
|
153
|
+
mongodb) driver="mongoose" ;;
|
|
154
|
+
*) return 0 ;;
|
|
155
|
+
esac
|
|
156
|
+
[[ -z "$driver" ]] && return 0
|
|
157
|
+
ensure_pkg "$driver"
|
|
158
|
+
}
|
|
159
|
+
|
|
56
160
|
pause() {
|
|
57
161
|
echo
|
|
58
162
|
read -n 1 -r -s -p "$(echo -e "${DIM}Press any key...${RESET}")" || true
|
|
@@ -154,29 +258,35 @@ run_adapter_convert() {
|
|
|
154
258
|
local input_file="$2"
|
|
155
259
|
local output_file="$3"
|
|
156
260
|
|
|
157
|
-
#
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
261
|
+
# Ensure @mostajs/orm-adapter is available — auto-install if missing
|
|
262
|
+
info "Checking @mostajs/orm-adapter..."
|
|
263
|
+
local adapter_base=""
|
|
264
|
+
|
|
265
|
+
# 1. Try local project install
|
|
266
|
+
if [[ -d "$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist" ]]; then
|
|
267
|
+
adapter_base="$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist"
|
|
268
|
+
ok "Using local install"
|
|
162
269
|
else
|
|
163
|
-
# Try
|
|
270
|
+
# 2. Try sibling (dev setup)
|
|
164
271
|
local cli_dir
|
|
165
272
|
cli_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
|
166
|
-
if [[ -
|
|
167
|
-
|
|
168
|
-
info "Using sibling
|
|
273
|
+
if [[ -d "$cli_dir/../mosta-orm-adapter/dist" ]]; then
|
|
274
|
+
adapter_base="$cli_dir/../mosta-orm-adapter/dist"
|
|
275
|
+
info "Using sibling dev install"
|
|
169
276
|
else
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
277
|
+
# 3. Offer auto-install
|
|
278
|
+
warn "Not installed locally"
|
|
279
|
+
if ensure_pkg "@mostajs/orm-adapter" "@mostajs/orm"; then
|
|
280
|
+
adapter_base="$PROJECT_ROOT/node_modules/@mostajs/orm-adapter/dist"
|
|
281
|
+
if [[ ! -d "$adapter_base" ]]; then
|
|
282
|
+
local resolved
|
|
283
|
+
resolved=$(resolve_pkg_path "@mostajs/orm-adapter") || {
|
|
284
|
+
err "Install reported success but module cannot be resolved."
|
|
285
|
+
return 1
|
|
286
|
+
}
|
|
287
|
+
adapter_base="$(dirname "$resolved")"
|
|
288
|
+
fi
|
|
289
|
+
ok "Installed"
|
|
180
290
|
else
|
|
181
291
|
err "Cannot proceed without the adapter."
|
|
182
292
|
return 1
|
|
@@ -184,23 +294,68 @@ run_adapter_convert() {
|
|
|
184
294
|
fi
|
|
185
295
|
fi
|
|
186
296
|
|
|
297
|
+
# Use subpath-specific import to avoid loading ALL adapters (and their
|
|
298
|
+
# transitive deps : ajv, ref-parser, openapi-parser...). For example,
|
|
299
|
+
# importing only prisma.adapter.js avoids pulling in ajv-draft-04 issues
|
|
300
|
+
# when the project only needs Prisma conversion.
|
|
301
|
+
local adapter_file
|
|
187
302
|
local adapter_class
|
|
188
303
|
case "$input_type" in
|
|
189
|
-
prisma) adapter_class="PrismaAdapter" ;;
|
|
190
|
-
openapi) adapter_class="OpenApiAdapter" ;;
|
|
191
|
-
jsonschema) adapter_class="JsonSchemaAdapter" ;;
|
|
304
|
+
prisma) adapter_file="$adapter_base/adapters/prisma.adapter.js" ; adapter_class="PrismaAdapter" ;;
|
|
305
|
+
openapi) adapter_file="$adapter_base/adapters/openapi.adapter.js" ; adapter_class="OpenApiAdapter" ;;
|
|
306
|
+
jsonschema) adapter_file="$adapter_base/adapters/jsonschema.adapter.js" ; adapter_class="JsonSchemaAdapter" ;;
|
|
192
307
|
*) err "Unknown input type: $input_type"; return 1 ;;
|
|
193
308
|
esac
|
|
194
309
|
|
|
310
|
+
if [[ ! -f "$adapter_file" ]]; then
|
|
311
|
+
warn "Subpath import $adapter_file not found — falling back to root index.js"
|
|
312
|
+
adapter_file="$adapter_base/index.js"
|
|
313
|
+
fi
|
|
314
|
+
|
|
195
315
|
cat > "$CONFIG_DIR/convert.mjs" << EOF
|
|
196
316
|
import { readFileSync, writeFileSync } from 'fs';
|
|
197
|
-
import { $adapter_class } from '$adapter_path';
|
|
198
317
|
|
|
199
|
-
|
|
318
|
+
let adapterModule;
|
|
319
|
+
try {
|
|
320
|
+
adapterModule = await import('$adapter_file');
|
|
321
|
+
} catch (e) {
|
|
322
|
+
console.error('Failed to import adapter from $adapter_file');
|
|
323
|
+
console.error('Reason :', e.message);
|
|
324
|
+
// Common issue : ajv/ref-parser/yaml resolution problems
|
|
325
|
+
if (e.message?.includes('ajv') || e.message?.includes('ref-parser') || e.message?.includes('yaml')) {
|
|
326
|
+
console.error();
|
|
327
|
+
console.error('Hint : this adapter requires peer deps that may be missing.');
|
|
328
|
+
console.error('Try : $PKG_MANAGER install ajv@^8 @apidevtools/json-schema-ref-parser@^11');
|
|
329
|
+
}
|
|
330
|
+
process.exit(2);
|
|
331
|
+
}
|
|
332
|
+
const { $adapter_class } = adapterModule;
|
|
333
|
+
if (!$adapter_class) {
|
|
334
|
+
console.error('$adapter_class not exported from the adapter module');
|
|
335
|
+
process.exit(3);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
let source;
|
|
339
|
+
try {
|
|
340
|
+
source = readFileSync('$input_file', 'utf8');
|
|
341
|
+
} catch (e) {
|
|
342
|
+
console.error('Cannot read input file : $input_file');
|
|
343
|
+
console.error('Reason :', e.message);
|
|
344
|
+
process.exit(4);
|
|
345
|
+
}
|
|
346
|
+
|
|
200
347
|
const adapter = new $adapter_class();
|
|
201
348
|
const warnings = [];
|
|
202
349
|
const input = '$input_type' === 'jsonschema' ? JSON.parse(source) : source;
|
|
203
|
-
|
|
350
|
+
|
|
351
|
+
let entities;
|
|
352
|
+
try {
|
|
353
|
+
entities = await adapter.toEntitySchema(input, { onWarning: w => warnings.push(w) });
|
|
354
|
+
} catch (e) {
|
|
355
|
+
console.error('Conversion failed :', e.message);
|
|
356
|
+
if (e.details) console.error('Details :', JSON.stringify(e.details, null, 2).slice(0, 500));
|
|
357
|
+
process.exit(5);
|
|
358
|
+
}
|
|
204
359
|
|
|
205
360
|
console.log('entities : ' + entities.length);
|
|
206
361
|
console.log('warnings : ' + warnings.length);
|
|
@@ -217,12 +372,28 @@ const code = header +
|
|
|
217
372
|
' entities.map(e => [e.name, e])\n' +
|
|
218
373
|
');\n';
|
|
219
374
|
|
|
220
|
-
|
|
221
|
-
|
|
375
|
+
try {
|
|
376
|
+
writeFileSync('$output_file', code);
|
|
377
|
+
// Also write .json (easier to load from ESM without TS support)
|
|
378
|
+
const jsonFile = '$output_file'.replace(/\.ts$/, '.json');
|
|
379
|
+
writeFileSync(jsonFile, JSON.stringify(entities, null, 2));
|
|
380
|
+
console.log('\u2713 Saved : $output_file');
|
|
381
|
+
console.log('\u2713 Saved : ' + jsonFile);
|
|
382
|
+
} catch (e) {
|
|
383
|
+
console.error('Cannot write output : ' + e.message);
|
|
384
|
+
process.exit(6);
|
|
385
|
+
}
|
|
222
386
|
EOF
|
|
223
387
|
|
|
388
|
+
local rc=0
|
|
224
389
|
node "$CONFIG_DIR/convert.mjs" 2>&1 | tee "$LOG_DIR/convert.log"
|
|
225
|
-
|
|
390
|
+
rc=${PIPESTATUS[0]}
|
|
391
|
+
if [[ $rc -ne 0 ]]; then
|
|
392
|
+
err "Conversion exited with code $rc"
|
|
393
|
+
info "See $LOG_DIR/convert.log for details"
|
|
394
|
+
return $rc
|
|
395
|
+
fi
|
|
396
|
+
return 0
|
|
226
397
|
}
|
|
227
398
|
|
|
228
399
|
# ============================================================
|
|
@@ -356,37 +527,43 @@ menu_databases() {
|
|
|
356
527
|
header
|
|
357
528
|
echo -e "${BOLD}${MAGENTA}▶ Database configuration${RESET}"
|
|
358
529
|
echo
|
|
359
|
-
echo -e "
|
|
360
|
-
echo
|
|
361
|
-
echo -e "
|
|
362
|
-
echo -e " ${CYAN}
|
|
363
|
-
echo -e " ${CYAN}
|
|
364
|
-
echo -e " ${CYAN}
|
|
365
|
-
echo -e " ${CYAN}
|
|
366
|
-
echo -e " ${CYAN}
|
|
367
|
-
echo -e " ${CYAN}9${RESET}) CockroachDB : ${DIM}${COCKROACH_URI:-<not set>}${RESET}"
|
|
530
|
+
echo -e "${DIM}Compatible with @mostajs/orm .env convention (see SecuAccessPro/.env.local)${RESET}"
|
|
531
|
+
echo
|
|
532
|
+
echo -e "${BOLD}Primary DB (single backend — 90% of apps) :${RESET}"
|
|
533
|
+
echo -e " ${CYAN}1${RESET}) DB_DIALECT : ${DIM}${DB_DIALECT:-<not set>}${RESET}"
|
|
534
|
+
echo -e " ${CYAN}2${RESET}) SGBD_URI : ${DIM}${SGBD_URI:-<not set>}${RESET}"
|
|
535
|
+
echo -e " ${CYAN}3${RESET}) DB_SCHEMA_STRATEGY : ${DIM}${DB_SCHEMA_STRATEGY:-update}${RESET}"
|
|
536
|
+
echo -e " ${CYAN}4${RESET}) DB_POOL_SIZE : ${DIM}${DB_POOL_SIZE:-20}${RESET}"
|
|
537
|
+
echo -e " ${CYAN}5${RESET}) DB_SHOW_SQL : ${DIM}${DB_SHOW_SQL:-false}${RESET}"
|
|
368
538
|
echo
|
|
369
|
-
echo -e "
|
|
370
|
-
echo -e " ${CYAN}
|
|
539
|
+
echo -e "${BOLD}Extra DBs (hybrid apps with Prisma Bridge) :${RESET}"
|
|
540
|
+
echo -e " ${CYAN}a${RESET}) Add extra binding ${DIM}(e.g. MongoDB for audit while PG is primary)${RESET}"
|
|
541
|
+
echo -e " ${CYAN}l${RESET}) List extra bindings : ${DIM}${EXTRA_BINDINGS:-<none>}${RESET}"
|
|
542
|
+
echo
|
|
543
|
+
echo -e "${BOLD}mosta-net + app :${RESET}"
|
|
544
|
+
echo -e " ${CYAN}u${RESET}) MOSTA_NET_URL : ${DIM}${MOSTA_NET_URL:-http://localhost:14488}${RESET}"
|
|
545
|
+
echo -e " ${CYAN}n${RESET}) MOSTA_NET_TRANSPORT : ${DIM}${MOSTA_NET_TRANSPORT:-rest}${RESET}"
|
|
546
|
+
echo -e " ${CYAN}p${RESET}) APP_PORT : ${DIM}${APP_PORT:-3000}${RESET}"
|
|
371
547
|
echo
|
|
372
548
|
echo -e " ${CYAN}t${RESET}) Test all connections"
|
|
549
|
+
echo -e " ${CYAN}e${RESET}) Export to .env.local in project"
|
|
373
550
|
echo -e " ${CYAN}r${RESET}) Reset config"
|
|
374
551
|
echo -e " ${CYAN}b${RESET}) Back"
|
|
375
552
|
echo
|
|
376
553
|
local choice; choice=$(ask "Choice" "1")
|
|
377
554
|
case "$choice" in
|
|
378
|
-
1)
|
|
379
|
-
2) save_var
|
|
380
|
-
3) save_var
|
|
381
|
-
4) save_var
|
|
382
|
-
5) save_var
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
p|P) save_var APP_PORT
|
|
388
|
-
n|N) save_var MOSTA_NET_PORT "$(ask 'mosta-net port' "${MOSTA_NET_PORT:-4447}")";;
|
|
555
|
+
1) prompt_dialect ;;
|
|
556
|
+
2) save_var SGBD_URI "$(ask 'SGBD_URI (path or connection string)' "${SGBD_URI:-./data.sqlite}")";;
|
|
557
|
+
3) save_var DB_SCHEMA_STRATEGY "$(ask 'DB_SCHEMA_STRATEGY (update|create|validate|none|create-drop)' "${DB_SCHEMA_STRATEGY:-update}")";;
|
|
558
|
+
4) save_var DB_POOL_SIZE "$(ask 'DB_POOL_SIZE' "${DB_POOL_SIZE:-20}")";;
|
|
559
|
+
5) save_var DB_SHOW_SQL "$(ask 'DB_SHOW_SQL (true|false)' "${DB_SHOW_SQL:-false}")";;
|
|
560
|
+
a|A) add_extra_binding ;;
|
|
561
|
+
l|L) list_extra_bindings; pause ;;
|
|
562
|
+
u|U) save_var MOSTA_NET_URL "$(ask 'MOSTA_NET_URL' "${MOSTA_NET_URL:-http://localhost:14488}")";;
|
|
563
|
+
n|N) save_var MOSTA_NET_TRANSPORT "$(ask 'MOSTA_NET_TRANSPORT (rest|sse|graphql|mcp|websocket|jsonrpc|grpc|odata)' "${MOSTA_NET_TRANSPORT:-rest}")";;
|
|
564
|
+
p|P) save_var APP_PORT "$(ask 'APP_PORT' "${APP_PORT:-3000}")";;
|
|
389
565
|
t|T) action_test_connections; return;;
|
|
566
|
+
e|E) export_env_local ;;
|
|
390
567
|
r|R) confirm "Really reset config?" && rm -f "$CONFIG_FILE" && ok "Reset";;
|
|
391
568
|
b|B) return;;
|
|
392
569
|
*) warn "Unknown";;
|
|
@@ -395,38 +572,235 @@ menu_databases() {
|
|
|
395
572
|
menu_databases
|
|
396
573
|
}
|
|
397
574
|
|
|
575
|
+
# Prompt user to choose a dialect from a numbered list
|
|
576
|
+
prompt_dialect() {
|
|
577
|
+
echo
|
|
578
|
+
echo "Pick a dialect:"
|
|
579
|
+
local i=1
|
|
580
|
+
local -a dialects=(sqlite postgres mysql mariadb mongodb mssql oracle db2 cockroachdb hana hsqldb spanner sybase)
|
|
581
|
+
for d in "${dialects[@]}"; do
|
|
582
|
+
echo -e " ${CYAN}$i${RESET}) $d"
|
|
583
|
+
i=$((i+1))
|
|
584
|
+
done
|
|
585
|
+
local num; num=$(ask "Number" 1)
|
|
586
|
+
local idx=$((num-1))
|
|
587
|
+
if [[ $idx -ge 0 && $idx -lt ${#dialects[@]} ]]; then
|
|
588
|
+
local d="${dialects[$idx]}"
|
|
589
|
+
save_var DB_DIALECT "$d"
|
|
590
|
+
# Suggest default URI for that dialect if SGBD_URI is empty
|
|
591
|
+
if [[ -z "${SGBD_URI:-}" ]]; then
|
|
592
|
+
local suggest
|
|
593
|
+
case "$d" in
|
|
594
|
+
sqlite) suggest="./data.sqlite" ;;
|
|
595
|
+
postgres) suggest="postgres://user:pw@localhost:5432/app" ;;
|
|
596
|
+
mysql|mariadb) suggest="mysql://user:pw@localhost:3306/app" ;;
|
|
597
|
+
mongodb) suggest="mongodb://localhost:27017/app" ;;
|
|
598
|
+
mssql) suggest="mssql://user:pw@localhost:1433/app" ;;
|
|
599
|
+
oracle) suggest="oracle://user:pw@localhost:1521/ORCLPDB" ;;
|
|
600
|
+
db2) suggest="db2://user:pw@localhost:50000/app" ;;
|
|
601
|
+
cockroachdb) suggest="postgres://user@localhost:26257/app" ;;
|
|
602
|
+
hana) suggest="hana://user:pw@localhost:39041" ;;
|
|
603
|
+
*) suggest="" ;;
|
|
604
|
+
esac
|
|
605
|
+
[[ -n "$suggest" ]] && save_var SGBD_URI "$(ask 'SGBD_URI' "$suggest")"
|
|
606
|
+
fi
|
|
607
|
+
else
|
|
608
|
+
warn "Invalid number"
|
|
609
|
+
fi
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
# Add an extra dialect binding for hybrid apps
|
|
613
|
+
add_extra_binding() {
|
|
614
|
+
local name; name=$(ask "Binding name (e.g. AuditLog, Reports) — used by the Prisma Bridge")
|
|
615
|
+
[[ -z "$name" ]] && return
|
|
616
|
+
local dialect; dialect=$(ask "Dialect for $name (sqlite|postgres|mongodb|oracle|...)")
|
|
617
|
+
[[ -z "$dialect" ]] && return
|
|
618
|
+
local uri; uri=$(ask "URI for $name")
|
|
619
|
+
[[ -z "$uri" ]] && return
|
|
620
|
+
local current="${EXTRA_BINDINGS:-}"
|
|
621
|
+
local new="${name}:${dialect}:${uri}"
|
|
622
|
+
if [[ -z "$current" ]]; then
|
|
623
|
+
save_var EXTRA_BINDINGS "$new"
|
|
624
|
+
else
|
|
625
|
+
save_var EXTRA_BINDINGS "${current};${new}"
|
|
626
|
+
fi
|
|
627
|
+
ok "Added: $name ($dialect @ $uri)"
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
list_extra_bindings() {
|
|
631
|
+
echo
|
|
632
|
+
if [[ -z "${EXTRA_BINDINGS:-}" ]]; then
|
|
633
|
+
dim " (none)"
|
|
634
|
+
return
|
|
635
|
+
fi
|
|
636
|
+
echo -e "${BOLD}Extra bindings:${RESET}"
|
|
637
|
+
local IFS=';'
|
|
638
|
+
for b in $EXTRA_BINDINGS; do
|
|
639
|
+
local name="${b%%:*}"
|
|
640
|
+
local rest="${b#*:}"
|
|
641
|
+
local dialect="${rest%%:*}"
|
|
642
|
+
local uri="${rest#*:}"
|
|
643
|
+
echo -e " ${CYAN}$name${RESET} → ${MAGENTA}$dialect${RESET} @ ${DIM}$uri${RESET}"
|
|
644
|
+
done
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
# Export a .env.local file compatible with @mostajs/orm convention
|
|
648
|
+
export_env_local() {
|
|
649
|
+
local target="$PROJECT_ROOT/.env.mostajs"
|
|
650
|
+
cat > "$target" <<EOF
|
|
651
|
+
# Generated by @mostajs/orm-cli on $(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
652
|
+
# Primary database
|
|
653
|
+
DB_DIALECT=${DB_DIALECT:-sqlite}
|
|
654
|
+
SGBD_URI=${SGBD_URI:-./data.sqlite}
|
|
655
|
+
DB_SCHEMA_STRATEGY=${DB_SCHEMA_STRATEGY:-update}
|
|
656
|
+
DB_POOL_SIZE=${DB_POOL_SIZE:-20}
|
|
657
|
+
DB_SHOW_SQL=${DB_SHOW_SQL:-false}
|
|
658
|
+
|
|
659
|
+
# mosta-net server
|
|
660
|
+
MOSTA_NET_URL=${MOSTA_NET_URL:-http://localhost:14488}
|
|
661
|
+
MOSTA_NET_TRANSPORT=${MOSTA_NET_TRANSPORT:-rest}
|
|
662
|
+
|
|
663
|
+
# App
|
|
664
|
+
APP_PORT=${APP_PORT:-3000}
|
|
665
|
+
|
|
666
|
+
# Extra bindings for Prisma Bridge (hybrid apps)
|
|
667
|
+
# Format: EXTRA_BINDINGS="ModelName:dialect:uri;OtherModel:dialect:uri"
|
|
668
|
+
EXTRA_BINDINGS=${EXTRA_BINDINGS:-}
|
|
669
|
+
EOF
|
|
670
|
+
ok "Exported : $target"
|
|
671
|
+
info "Review, rename to .env.local, and commit to your .env.example (without secrets)"
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
# Auto-detect mosta-orm dialect from URI scheme
|
|
675
|
+
detect_dialect_from_uri() {
|
|
676
|
+
local uri="$1"
|
|
677
|
+
case "$uri" in
|
|
678
|
+
mongodb://*|mongodb+srv://*) echo "mongodb" ;;
|
|
679
|
+
postgres://*|postgresql://*) echo "postgres" ;;
|
|
680
|
+
mysql://*) echo "mysql" ;;
|
|
681
|
+
mariadb://*) echo "mariadb" ;;
|
|
682
|
+
mssql://*|sqlserver://*) echo "mssql" ;;
|
|
683
|
+
oracle://*) echo "oracle" ;;
|
|
684
|
+
db2://*) echo "db2" ;;
|
|
685
|
+
hana://*) echo "hana" ;;
|
|
686
|
+
cockroachdb://*) echo "cockroachdb" ;;
|
|
687
|
+
spanner://*) echo "spanner" ;;
|
|
688
|
+
sybase://*) echo "sybase" ;;
|
|
689
|
+
sqlite://*|sqlite:*|*.sqlite|*.db|:memory:) echo "sqlite" ;;
|
|
690
|
+
*) echo "unknown" ;;
|
|
691
|
+
esac
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
# Strip scheme prefix from URI (used for SQLite path)
|
|
695
|
+
strip_uri_scheme() {
|
|
696
|
+
local uri="$1"
|
|
697
|
+
case "$uri" in
|
|
698
|
+
sqlite://*) echo "${uri#sqlite://}" ;;
|
|
699
|
+
sqlite:*) echo "${uri#sqlite:}" ;;
|
|
700
|
+
*) echo "$uri" ;;
|
|
701
|
+
esac
|
|
702
|
+
}
|
|
703
|
+
|
|
398
704
|
action_test_connections() {
|
|
399
705
|
header
|
|
400
|
-
echo -e "${BOLD}${MAGENTA}▶ Testing connections${RESET}"
|
|
706
|
+
echo -e "${BOLD}${MAGENTA}▶ Testing connections (via @mostajs/orm)${RESET}"
|
|
401
707
|
echo
|
|
402
708
|
load_env
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
local
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
709
|
+
|
|
710
|
+
# Collect all configured URIs as (dialect, uri) pairs
|
|
711
|
+
local -a pairs=()
|
|
712
|
+
|
|
713
|
+
# Primary DB
|
|
714
|
+
if [[ -n "${DB_DIALECT:-}" && -n "${SGBD_URI:-}" ]]; then
|
|
715
|
+
pairs+=("${DB_DIALECT}|${SGBD_URI}")
|
|
716
|
+
fi
|
|
717
|
+
|
|
718
|
+
# Extra bindings (format: name:dialect:uri;name:dialect:uri)
|
|
719
|
+
if [[ -n "${EXTRA_BINDINGS:-}" ]]; then
|
|
720
|
+
local IFS=';'
|
|
721
|
+
for b in $EXTRA_BINDINGS; do
|
|
722
|
+
local rest="${b#*:}" # strip name
|
|
723
|
+
local dialect="${rest%%:*}"
|
|
724
|
+
local uri="${rest#*:}"
|
|
725
|
+
pairs+=("$dialect|$uri")
|
|
726
|
+
done
|
|
727
|
+
IFS=$' \t\n'
|
|
728
|
+
fi
|
|
729
|
+
|
|
730
|
+
if [[ ${#pairs[@]} -eq 0 ]]; then
|
|
731
|
+
warn "No URIs configured. Go to menu 2 first."
|
|
732
|
+
pause; return
|
|
733
|
+
fi
|
|
734
|
+
|
|
735
|
+
# Ensure @mostajs/orm is installed (test uses its native testConnection)
|
|
736
|
+
info "Checking @mostajs/orm installation..."
|
|
737
|
+
if ! ensure_pkg "@mostajs/orm"; then
|
|
738
|
+
err "Cannot test connections without @mostajs/orm"
|
|
739
|
+
pause; return
|
|
740
|
+
fi
|
|
741
|
+
|
|
742
|
+
# Ensure drivers for each dialect being tested
|
|
743
|
+
info "Checking drivers..."
|
|
744
|
+
for p in "${pairs[@]}"; do
|
|
745
|
+
local d="${p%%|*}"
|
|
746
|
+
ensure_dialect_driver "$d" || warn "Driver for $d may be missing"
|
|
427
747
|
done
|
|
428
748
|
|
|
429
|
-
|
|
749
|
+
local orm_path
|
|
750
|
+
orm_path=$(resolve_pkg_path "@mostajs/orm") || {
|
|
751
|
+
err "Cannot resolve @mostajs/orm"
|
|
752
|
+
pause; return
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
# Build a small node script that tests each connection
|
|
756
|
+
local -a args=()
|
|
757
|
+
for p in "${pairs[@]}"; do
|
|
758
|
+
args+=("$p")
|
|
759
|
+
done
|
|
760
|
+
|
|
761
|
+
cat > "$CONFIG_DIR/test-connections.mjs" <<EOF
|
|
762
|
+
import { getDialect } from '$orm_path';
|
|
763
|
+
|
|
764
|
+
const pairs = process.argv.slice(2).map(s => {
|
|
765
|
+
const i = s.indexOf('|');
|
|
766
|
+
return [s.slice(0, i), s.slice(i + 1)];
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
function stripScheme(uri) {
|
|
770
|
+
if (uri.startsWith('sqlite://')) return uri.slice(9);
|
|
771
|
+
if (uri.startsWith('sqlite:')) return uri.slice(7);
|
|
772
|
+
return uri;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
let ok = 0, fail = 0;
|
|
776
|
+
for (const [dialect, rawUri] of pairs) {
|
|
777
|
+
const uri = dialect === 'sqlite' ? stripScheme(rawUri) : rawUri;
|
|
778
|
+
process.stdout.write(dialect.padEnd(12) + ' ' + rawUri + '\n');
|
|
779
|
+
try {
|
|
780
|
+
const d = await getDialect({ dialect, uri });
|
|
781
|
+
const alive = await d.testConnection();
|
|
782
|
+
if (alive) {
|
|
783
|
+
console.log(' \u2713 reachable');
|
|
784
|
+
ok++;
|
|
785
|
+
} else {
|
|
786
|
+
console.log(' \u2717 testConnection returned false');
|
|
787
|
+
fail++;
|
|
788
|
+
}
|
|
789
|
+
await d.disconnect().catch(() => {});
|
|
790
|
+
} catch (e) {
|
|
791
|
+
console.error(' \u2717 ' + (e.message ?? e));
|
|
792
|
+
if (e.code) console.error(' code : ' + e.code);
|
|
793
|
+
fail++;
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
console.log();
|
|
797
|
+
console.log('Results : ' + ok + ' reachable, ' + fail + ' failed');
|
|
798
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
799
|
+
EOF
|
|
800
|
+
|
|
801
|
+
cd "$PROJECT_ROOT"
|
|
802
|
+
node "$CONFIG_DIR/test-connections.mjs" "${args[@]}" 2>&1 | tee "$LOG_DIR/test-connections.log"
|
|
803
|
+
echo
|
|
430
804
|
pause
|
|
431
805
|
}
|
|
432
806
|
|
|
@@ -445,46 +819,139 @@ action_init_dialects() {
|
|
|
445
819
|
pause; return
|
|
446
820
|
fi
|
|
447
821
|
|
|
448
|
-
|
|
449
|
-
local
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
822
|
+
# Collect configured dialects from DB_DIALECT/SGBD_URI + EXTRA_BINDINGS
|
|
823
|
+
local -a configured=()
|
|
824
|
+
local -a dialect_names=()
|
|
825
|
+
|
|
826
|
+
if [[ -n "${DB_DIALECT:-}" && -n "${SGBD_URI:-}" ]]; then
|
|
827
|
+
configured+=("${DB_DIALECT}:${SGBD_URI}")
|
|
828
|
+
dialect_names+=("$DB_DIALECT")
|
|
829
|
+
fi
|
|
830
|
+
|
|
831
|
+
if [[ -n "${EXTRA_BINDINGS:-}" ]]; then
|
|
832
|
+
local IFS=';'
|
|
833
|
+
for b in $EXTRA_BINDINGS; do
|
|
834
|
+
local rest="${b#*:}"
|
|
835
|
+
local dialect="${rest%%:*}"
|
|
836
|
+
local uri="${rest#*:}"
|
|
837
|
+
configured+=("${dialect}:${uri}")
|
|
838
|
+
dialect_names+=("$dialect")
|
|
839
|
+
done
|
|
840
|
+
IFS=$' \t\n'
|
|
841
|
+
fi
|
|
842
|
+
|
|
843
|
+
if [[ ${#configured[@]} -eq 0 ]]; then
|
|
844
|
+
err "No URIs set. Menu 2 first."
|
|
845
|
+
pause; return
|
|
846
|
+
fi
|
|
847
|
+
|
|
848
|
+
info "Will attempt to initialize:"
|
|
849
|
+
for item in "${configured[@]}"; do
|
|
850
|
+
dim " ${item%%:*} → ${item#*:}"
|
|
453
851
|
done
|
|
454
|
-
|
|
852
|
+
echo
|
|
455
853
|
|
|
456
854
|
confirm "Proceed?" || return
|
|
457
855
|
|
|
458
|
-
|
|
856
|
+
# ---- Step 1 : ensure @mostajs/orm is installed ----
|
|
857
|
+
info "Step 1/3 : checking @mostajs/orm installation..."
|
|
858
|
+
if ! ensure_pkg "@mostajs/orm"; then
|
|
859
|
+
err "Cannot initialize dialects without @mostajs/orm"
|
|
860
|
+
pause; return
|
|
861
|
+
fi
|
|
862
|
+
ok "@mostajs/orm available"
|
|
863
|
+
|
|
864
|
+
# ---- Step 2 : ensure drivers for each dialect are installed ----
|
|
865
|
+
info "Step 2/3 : checking dialect drivers..."
|
|
866
|
+
for dialect in "${dialect_names[@]}"; do
|
|
867
|
+
ensure_dialect_driver "$dialect" || warn "Driver for $dialect may be missing"
|
|
868
|
+
done
|
|
869
|
+
ok "Drivers checked"
|
|
870
|
+
|
|
871
|
+
# ---- Step 3 : resolve absolute path to @mostajs/orm (avoids import resolution issues) ----
|
|
872
|
+
local orm_path
|
|
873
|
+
orm_path=$(resolve_pkg_path "@mostajs/orm") || {
|
|
874
|
+
err "Could not resolve @mostajs/orm path even after install"
|
|
875
|
+
pause; return
|
|
876
|
+
}
|
|
877
|
+
dim "Using @mostajs/orm at : $orm_path"
|
|
878
|
+
echo
|
|
879
|
+
|
|
880
|
+
# Pass each dialect:uri pair as argv to the node script
|
|
881
|
+
cat > "$CONFIG_DIR/init-all.mjs" <<EOF
|
|
882
|
+
// Auto-generated by mostajs-cli — runs from project root
|
|
459
883
|
import { readFileSync } from 'fs';
|
|
460
|
-
import { getDialect } from '
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
884
|
+
import { getDialect } from '$orm_path';
|
|
885
|
+
|
|
886
|
+
let entities;
|
|
887
|
+
try {
|
|
888
|
+
entities = JSON.parse(readFileSync('$GENERATED_DIR/entities.json', 'utf8'));
|
|
889
|
+
} catch (e) {
|
|
890
|
+
console.error('Cannot load entities.json — run menu 1 (Convert) first.');
|
|
891
|
+
console.error('Reason : ' + e.message);
|
|
892
|
+
process.exit(1);
|
|
893
|
+
}
|
|
894
|
+
|
|
895
|
+
function stripScheme(uri) {
|
|
896
|
+
if (uri.startsWith('sqlite://')) return uri.slice(9);
|
|
897
|
+
if (uri.startsWith('sqlite:')) return uri.slice(7);
|
|
898
|
+
return uri;
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
const pairs = process.argv.slice(2).map(s => {
|
|
902
|
+
const i = s.indexOf('|');
|
|
903
|
+
return [s.slice(0, i), s.slice(i + 1)];
|
|
904
|
+
});
|
|
905
|
+
|
|
906
|
+
let ok = 0, fail = 0;
|
|
907
|
+
const schemaStrategy = process.env.DB_SCHEMA_STRATEGY ?? 'update';
|
|
908
|
+
const poolSize = parseInt(process.env.DB_POOL_SIZE ?? '20', 10);
|
|
909
|
+
const showSql = process.env.DB_SHOW_SQL === 'true';
|
|
910
|
+
|
|
911
|
+
for (const [dialect, rawUri] of pairs) {
|
|
912
|
+
const uri = dialect === 'sqlite' ? stripScheme(rawUri) : rawUri;
|
|
913
|
+
process.stdout.write('→ ' + dialect.padEnd(12) + ' : ' + rawUri + '\n');
|
|
476
914
|
try {
|
|
477
|
-
const d = await getDialect({ dialect, uri, schemaStrategy
|
|
915
|
+
const d = await getDialect({ dialect, uri, schemaStrategy, poolSize, showSql });
|
|
478
916
|
await d.initSchema(entities);
|
|
479
|
-
console.log(
|
|
480
|
-
await d.disconnect();
|
|
917
|
+
console.log(' ✓ ' + dialect + ' ready (' + entities.length + ' entities)');
|
|
918
|
+
await d.disconnect().catch(() => {});
|
|
919
|
+
ok++;
|
|
481
920
|
} catch (e) {
|
|
482
|
-
console.error(
|
|
921
|
+
console.error(' ✗ ' + dialect + ' failed : ' + (e.message ?? e));
|
|
922
|
+
if (e.code) console.error(' code : ' + e.code);
|
|
923
|
+
fail++;
|
|
483
924
|
}
|
|
484
925
|
}
|
|
926
|
+
console.log();
|
|
927
|
+
console.log('Summary : ' + ok + ' succeeded, ' + fail + ' failed');
|
|
928
|
+
process.exit(fail > 0 ? 1 : 0);
|
|
485
929
|
EOF
|
|
486
|
-
|
|
487
|
-
|
|
930
|
+
|
|
931
|
+
info "Step 3/3 : running initialization..."
|
|
932
|
+
cd "$PROJECT_ROOT" || return
|
|
933
|
+
|
|
934
|
+
# Pass each dialect:uri as an argv pair
|
|
935
|
+
local -a args=()
|
|
936
|
+
for item in "${configured[@]}"; do
|
|
937
|
+
local d="${item%%:*}"
|
|
938
|
+
local u="${item#*:}"
|
|
939
|
+
args+=("$d|$u")
|
|
940
|
+
done
|
|
941
|
+
|
|
942
|
+
if node "$CONFIG_DIR/init-all.mjs" "${args[@]}" 2>&1 | tee "$LOG_DIR/init.log"; then
|
|
943
|
+
echo
|
|
944
|
+
ok "Initialization complete"
|
|
945
|
+
else
|
|
946
|
+
echo
|
|
947
|
+
warn "One or more dialects failed — check the log above"
|
|
948
|
+
info "Log : $LOG_DIR/init.log"
|
|
949
|
+
echo
|
|
950
|
+
info "Common fixes :"
|
|
951
|
+
dim " - Verify the URI in menu 2 is reachable (menu 2 → T)"
|
|
952
|
+
dim " - Install missing driver : $PKG_MANAGER install <driver>"
|
|
953
|
+
dim " - Oracle/DB2/HANA need native libs installed on your system"
|
|
954
|
+
fi
|
|
488
955
|
pause
|
|
489
956
|
}
|
|
490
957
|
|
|
@@ -587,20 +1054,52 @@ action_curl_test() {
|
|
|
587
1054
|
header
|
|
588
1055
|
echo -e "${BOLD}${MAGENTA}▶ curl smoke test${RESET}"
|
|
589
1056
|
echo
|
|
1057
|
+
|
|
1058
|
+
if ! command -v curl >/dev/null 2>&1; then
|
|
1059
|
+
err "curl is not installed"
|
|
1060
|
+
info "Install with : sudo apt install curl"
|
|
1061
|
+
return 1
|
|
1062
|
+
fi
|
|
1063
|
+
|
|
1064
|
+
local any_reachable=0
|
|
590
1065
|
for url in \
|
|
591
1066
|
"http://localhost:${APP_PORT:-3000}/" \
|
|
592
1067
|
"http://localhost:${APP_PORT:-3000}/api/health" \
|
|
593
1068
|
"http://localhost:${MOSTA_NET_PORT:-4447}/" \
|
|
594
1069
|
"http://localhost:${MOSTA_NET_PORT:-4447}/mcp"; do
|
|
595
1070
|
info "GET $url"
|
|
596
|
-
|
|
1071
|
+
local output
|
|
1072
|
+
output=$(curl -s -o /dev/null -w " status=%{http_code} time=%{time_total}s" --max-time 5 "$url" 2>&1)
|
|
1073
|
+
local rc=$?
|
|
1074
|
+
if [[ $rc -eq 0 ]]; then
|
|
1075
|
+
echo "$output"
|
|
1076
|
+
any_reachable=1
|
|
1077
|
+
else
|
|
1078
|
+
err " unreachable (curl exit $rc — check service is running)"
|
|
1079
|
+
fi
|
|
597
1080
|
done
|
|
1081
|
+
[[ $any_reachable -eq 0 ]] && {
|
|
1082
|
+
echo
|
|
1083
|
+
warn "No endpoints reachable. Start services first (menu 5)."
|
|
1084
|
+
}
|
|
598
1085
|
}
|
|
599
1086
|
|
|
600
1087
|
run_in_project() {
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
1088
|
+
local cmd="$1"
|
|
1089
|
+
cd "$PROJECT_ROOT" || { err "Cannot cd to project root"; return 1; }
|
|
1090
|
+
info "Running: $cmd"
|
|
1091
|
+
set +e
|
|
1092
|
+
eval "$cmd"
|
|
1093
|
+
local rc=$?
|
|
1094
|
+
set -e
|
|
1095
|
+
if [[ $rc -ne 0 ]]; then
|
|
1096
|
+
err "Command exited with code $rc"
|
|
1097
|
+
info "Check the output above, or common issues :"
|
|
1098
|
+
dim " - Tests failing → fix them, or skip with a flag"
|
|
1099
|
+
dim " - 'command not found' → install missing dev tools"
|
|
1100
|
+
dim " - Port conflict → stop other services first (menu 5 → 3)"
|
|
1101
|
+
fi
|
|
1102
|
+
return $rc
|
|
604
1103
|
}
|
|
605
1104
|
|
|
606
1105
|
# ============================================================
|
|
@@ -982,6 +1481,10 @@ EOF
|
|
|
982
1481
|
# MAIN
|
|
983
1482
|
# ============================================================
|
|
984
1483
|
|
|
1484
|
+
# If this script is being *sourced*, stop here — don't start the menu.
|
|
1485
|
+
# This lets other scripts (e.g. tests) reuse helper functions.
|
|
1486
|
+
[[ "${BASH_SOURCE[0]}" != "${0}" ]] && return 0 2>/dev/null
|
|
1487
|
+
|
|
985
1488
|
# Non-interactive mode if args provided
|
|
986
1489
|
if [[ $# -gt 0 ]]; then
|
|
987
1490
|
run_subcommand "$@"
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mostajs/orm-cli",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Universal CLI to integrate @mostajs/orm into any project — auto-detects Prisma, OpenAPI, JSON Schema. Interactive menu + subcommands. 13 databases.",
|
|
5
5
|
"author": "Dr Hamid MADANI <drmdh@msn.com>",
|
|
6
6
|
"license": "AGPL-3.0-or-later",
|
|
@@ -40,5 +40,9 @@
|
|
|
40
40
|
"linux",
|
|
41
41
|
"darwin",
|
|
42
42
|
"win32"
|
|
43
|
-
]
|
|
43
|
+
],
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"@mostajs/orm": "^1.9.2",
|
|
46
|
+
"better-sqlite3": "^12.9.0"
|
|
47
|
+
}
|
|
44
48
|
}
|