@hasna/cloud 0.1.4 → 0.1.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 +37 -0
- package/dist/adapter.d.ts +2 -0
- package/dist/adapter.d.ts.map +1 -1
- package/dist/cli/index.js +120 -13
- package/dist/index.js +117 -13
- package/dist/mcp/index.js +120 -13
- package/dist/sync.d.ts +6 -2
- package/dist/sync.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# @hasna/cloud
|
|
2
|
+
|
|
3
|
+
Shared cloud infrastructure — database adapter (SQLite + PostgreSQL), sync engine, feedback system, unified dotfile config
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@hasna/cloud)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
## Install
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @hasna/cloud
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## CLI Usage
|
|
15
|
+
|
|
16
|
+
```bash
|
|
17
|
+
cloud --help
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
- `cloud setup`
|
|
21
|
+
- `cloud status`
|
|
22
|
+
- `cloud sync push`
|
|
23
|
+
- `cloud sync pull`
|
|
24
|
+
- `cloud feedback`
|
|
25
|
+
- `cloud migrate`
|
|
26
|
+
|
|
27
|
+
## MCP Server
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
cloud-mcp
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
4 tools available.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
Apache-2.0 -- see [LICENSE](LICENSE)
|
package/dist/adapter.d.ts
CHANGED
|
@@ -26,6 +26,8 @@ export declare class SqliteAdapter implements DbAdapter {
|
|
|
26
26
|
get(sql: string, ...params: any[]): any;
|
|
27
27
|
all(sql: string, ...params: any[]): any[];
|
|
28
28
|
exec(sql: string): void;
|
|
29
|
+
/** Passthrough to bun:sqlite's .query() for compatibility with code that uses db.query() */
|
|
30
|
+
query(sql: string): import("bun:sqlite").Statement<unknown, import("bun:sqlite").SQLQueryBindings[] | [null] | [string] | [number] | [bigint] | [false] | [true] | [Uint8Array<ArrayBufferLike>] | [Uint8ClampedArray<ArrayBufferLike>] | [Uint16Array<ArrayBufferLike>] | [Uint32Array<ArrayBufferLike>] | [Int8Array<ArrayBufferLike>] | [Int16Array<ArrayBufferLike>] | [Int32Array<ArrayBufferLike>] | [BigUint64Array<ArrayBufferLike>] | [BigInt64Array<ArrayBufferLike>] | [Float16Array<ArrayBufferLike>] | [Float32Array<ArrayBufferLike>] | [Float64Array<ArrayBufferLike>] | [Record<string, string | number | bigint | boolean | NodeJS.TypedArray<ArrayBufferLike> | null>]>;
|
|
29
31
|
prepare(sql: string): PreparedStatement;
|
|
30
32
|
close(): void;
|
|
31
33
|
transaction<T>(fn: () => T): T;
|
package/dist/adapter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -11026,6 +11026,9 @@ class SqliteAdapter {
|
|
|
11026
11026
|
exec(sql) {
|
|
11027
11027
|
this.db.exec(sql);
|
|
11028
11028
|
}
|
|
11029
|
+
query(sql) {
|
|
11030
|
+
return this.db.query(sql);
|
|
11031
|
+
}
|
|
11029
11032
|
prepare(sql) {
|
|
11030
11033
|
const stmt = this.db.prepare(sql);
|
|
11031
11034
|
return {
|
|
@@ -11334,13 +11337,56 @@ function heuristicOrder(tables) {
|
|
|
11334
11337
|
});
|
|
11335
11338
|
return sorted;
|
|
11336
11339
|
}
|
|
11340
|
+
function getSqlitePrimaryKeys(adapter, table) {
|
|
11341
|
+
try {
|
|
11342
|
+
const cols = adapter.all(`PRAGMA table_info("${table}")`);
|
|
11343
|
+
const pkCols = cols.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
|
|
11344
|
+
return pkCols;
|
|
11345
|
+
} catch {
|
|
11346
|
+
return [];
|
|
11347
|
+
}
|
|
11348
|
+
}
|
|
11349
|
+
async function getPgPrimaryKeys(adapter, table) {
|
|
11350
|
+
try {
|
|
11351
|
+
const rows = await adapter.all(`
|
|
11352
|
+
SELECT kcu.column_name, kcu.ordinal_position
|
|
11353
|
+
FROM information_schema.table_constraints tc
|
|
11354
|
+
JOIN information_schema.key_column_usage kcu
|
|
11355
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
11356
|
+
AND tc.table_schema = kcu.table_schema
|
|
11357
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
11358
|
+
AND tc.table_schema = 'public'
|
|
11359
|
+
AND tc.table_name = '${table}'
|
|
11360
|
+
ORDER BY kcu.ordinal_position
|
|
11361
|
+
`);
|
|
11362
|
+
return rows.map((r) => r.column_name);
|
|
11363
|
+
} catch {
|
|
11364
|
+
return [];
|
|
11365
|
+
}
|
|
11366
|
+
}
|
|
11367
|
+
async function detectPrimaryKeys(adapter, table) {
|
|
11368
|
+
if (isAsyncAdapter(adapter)) {
|
|
11369
|
+
return getPgPrimaryKeys(adapter, table);
|
|
11370
|
+
}
|
|
11371
|
+
return getSqlitePrimaryKeys(adapter, table);
|
|
11372
|
+
}
|
|
11373
|
+
async function resolvePrimaryKeys(source, target, table, pkOption) {
|
|
11374
|
+
if (pkOption) {
|
|
11375
|
+
return Array.isArray(pkOption) ? pkOption : [pkOption];
|
|
11376
|
+
}
|
|
11377
|
+
let pks = await detectPrimaryKeys(source, table);
|
|
11378
|
+
if (pks.length === 0) {
|
|
11379
|
+
pks = await detectPrimaryKeys(target, table);
|
|
11380
|
+
}
|
|
11381
|
+
return pks;
|
|
11382
|
+
}
|
|
11337
11383
|
async function syncTransfer(source, target, options, _direction) {
|
|
11338
11384
|
const {
|
|
11339
11385
|
tables,
|
|
11340
11386
|
onProgress,
|
|
11341
11387
|
batchSize = 100,
|
|
11342
11388
|
conflictColumn = "updated_at",
|
|
11343
|
-
primaryKey
|
|
11389
|
+
primaryKey: pkOption
|
|
11344
11390
|
} = options;
|
|
11345
11391
|
const results = [];
|
|
11346
11392
|
for (let i = 0;i < tables.length; i++) {
|
|
@@ -11375,10 +11421,45 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
11375
11421
|
results.push(result);
|
|
11376
11422
|
continue;
|
|
11377
11423
|
}
|
|
11424
|
+
const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
|
|
11378
11425
|
const columns = Object.keys(rows[0]);
|
|
11379
|
-
|
|
11380
|
-
|
|
11381
|
-
|
|
11426
|
+
if (pkColumns.length === 0) {
|
|
11427
|
+
result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
|
|
11428
|
+
onProgress?.({
|
|
11429
|
+
table,
|
|
11430
|
+
phase: "writing",
|
|
11431
|
+
rowsRead: result.rowsRead,
|
|
11432
|
+
rowsWritten: 0,
|
|
11433
|
+
totalTables: tables.length,
|
|
11434
|
+
currentTableIndex: i
|
|
11435
|
+
});
|
|
11436
|
+
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
11437
|
+
const batch = rows.slice(offset, offset + batchSize);
|
|
11438
|
+
try {
|
|
11439
|
+
if (isAsyncAdapter(target)) {
|
|
11440
|
+
await batchInsertPg(target, table, columns, batch);
|
|
11441
|
+
} else {
|
|
11442
|
+
batchInsertSqlite(target, table, columns, batch);
|
|
11443
|
+
}
|
|
11444
|
+
result.rowsWritten += batch.length;
|
|
11445
|
+
} catch (err) {
|
|
11446
|
+
result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
|
|
11447
|
+
}
|
|
11448
|
+
}
|
|
11449
|
+
onProgress?.({
|
|
11450
|
+
table,
|
|
11451
|
+
phase: "done",
|
|
11452
|
+
rowsRead: result.rowsRead,
|
|
11453
|
+
rowsWritten: result.rowsWritten,
|
|
11454
|
+
totalTables: tables.length,
|
|
11455
|
+
currentTableIndex: i
|
|
11456
|
+
});
|
|
11457
|
+
results.push(result);
|
|
11458
|
+
continue;
|
|
11459
|
+
}
|
|
11460
|
+
const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
|
|
11461
|
+
if (missingPks.length > 0) {
|
|
11462
|
+
result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
|
|
11382
11463
|
results.push(result);
|
|
11383
11464
|
continue;
|
|
11384
11465
|
}
|
|
@@ -11390,14 +11471,14 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
11390
11471
|
totalTables: tables.length,
|
|
11391
11472
|
currentTableIndex: i
|
|
11392
11473
|
});
|
|
11393
|
-
const updateCols = columns.filter((c) => c
|
|
11474
|
+
const updateCols = columns.filter((c) => !pkColumns.includes(c));
|
|
11394
11475
|
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
11395
11476
|
const batch = rows.slice(offset, offset + batchSize);
|
|
11396
11477
|
try {
|
|
11397
11478
|
if (isAsyncAdapter(target)) {
|
|
11398
|
-
await batchUpsertPg(target, table, columns, updateCols,
|
|
11479
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
11399
11480
|
} else {
|
|
11400
|
-
batchUpsertSqlite(target, table, columns, updateCols,
|
|
11481
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
11401
11482
|
}
|
|
11402
11483
|
result.rowsWritten += batch.length;
|
|
11403
11484
|
} catch (err) {
|
|
@@ -11427,7 +11508,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
11427
11508
|
}
|
|
11428
11509
|
return results;
|
|
11429
11510
|
}
|
|
11430
|
-
async function batchUpsertPg(target, table, columns, updateCols,
|
|
11511
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
11431
11512
|
if (batch.length === 0)
|
|
11432
11513
|
return;
|
|
11433
11514
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -11435,20 +11516,43 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, bat
|
|
|
11435
11516
|
const offset = rowIdx * columns.length;
|
|
11436
11517
|
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
11437
11518
|
}).join(", ");
|
|
11438
|
-
const
|
|
11519
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
11520
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
11439
11521
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
11440
|
-
ON CONFLICT (
|
|
11522
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
11441
11523
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
11442
11524
|
await target.run(sql, ...params);
|
|
11443
11525
|
}
|
|
11444
|
-
function batchUpsertSqlite(target, table, columns, updateCols,
|
|
11526
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
11445
11527
|
if (batch.length === 0)
|
|
11446
11528
|
return;
|
|
11447
11529
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
11448
11530
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
11449
|
-
const
|
|
11531
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
11532
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
11450
11533
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
11451
|
-
ON CONFLICT (
|
|
11534
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
11535
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
11536
|
+
target.run(sql, ...params);
|
|
11537
|
+
}
|
|
11538
|
+
async function batchInsertPg(target, table, columns, batch) {
|
|
11539
|
+
if (batch.length === 0)
|
|
11540
|
+
return;
|
|
11541
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
11542
|
+
const valuePlaceholders = batch.map((_, rowIdx) => {
|
|
11543
|
+
const offset = rowIdx * columns.length;
|
|
11544
|
+
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
11545
|
+
}).join(", ");
|
|
11546
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
11547
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
11548
|
+
await target.run(sql, ...params);
|
|
11549
|
+
}
|
|
11550
|
+
function batchInsertSqlite(target, table, columns, batch) {
|
|
11551
|
+
if (batch.length === 0)
|
|
11552
|
+
return;
|
|
11553
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
11554
|
+
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
11555
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
11452
11556
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
11453
11557
|
target.run(sql, ...params);
|
|
11454
11558
|
}
|
|
@@ -11639,6 +11743,9 @@ class SqliteAdapter2 {
|
|
|
11639
11743
|
exec(sql) {
|
|
11640
11744
|
this.db.exec(sql);
|
|
11641
11745
|
}
|
|
11746
|
+
query(sql) {
|
|
11747
|
+
return this.db.query(sql);
|
|
11748
|
+
}
|
|
11642
11749
|
prepare(sql) {
|
|
11643
11750
|
const stmt = this.db.prepare(sql);
|
|
11644
11751
|
return {
|
package/dist/index.js
CHANGED
|
@@ -5007,6 +5007,9 @@ class SqliteAdapter {
|
|
|
5007
5007
|
exec(sql) {
|
|
5008
5008
|
this.db.exec(sql);
|
|
5009
5009
|
}
|
|
5010
|
+
query(sql) {
|
|
5011
|
+
return this.db.query(sql);
|
|
5012
|
+
}
|
|
5010
5013
|
prepare(sql) {
|
|
5011
5014
|
const stmt = this.db.prepare(sql);
|
|
5012
5015
|
return {
|
|
@@ -9386,13 +9389,56 @@ function heuristicOrder(tables) {
|
|
|
9386
9389
|
});
|
|
9387
9390
|
return sorted;
|
|
9388
9391
|
}
|
|
9392
|
+
function getSqlitePrimaryKeys(adapter, table) {
|
|
9393
|
+
try {
|
|
9394
|
+
const cols = adapter.all(`PRAGMA table_info("${table}")`);
|
|
9395
|
+
const pkCols = cols.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
|
|
9396
|
+
return pkCols;
|
|
9397
|
+
} catch {
|
|
9398
|
+
return [];
|
|
9399
|
+
}
|
|
9400
|
+
}
|
|
9401
|
+
async function getPgPrimaryKeys(adapter, table) {
|
|
9402
|
+
try {
|
|
9403
|
+
const rows = await adapter.all(`
|
|
9404
|
+
SELECT kcu.column_name, kcu.ordinal_position
|
|
9405
|
+
FROM information_schema.table_constraints tc
|
|
9406
|
+
JOIN information_schema.key_column_usage kcu
|
|
9407
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
9408
|
+
AND tc.table_schema = kcu.table_schema
|
|
9409
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
9410
|
+
AND tc.table_schema = 'public'
|
|
9411
|
+
AND tc.table_name = '${table}'
|
|
9412
|
+
ORDER BY kcu.ordinal_position
|
|
9413
|
+
`);
|
|
9414
|
+
return rows.map((r) => r.column_name);
|
|
9415
|
+
} catch {
|
|
9416
|
+
return [];
|
|
9417
|
+
}
|
|
9418
|
+
}
|
|
9419
|
+
async function detectPrimaryKeys(adapter, table) {
|
|
9420
|
+
if (isAsyncAdapter(adapter)) {
|
|
9421
|
+
return getPgPrimaryKeys(adapter, table);
|
|
9422
|
+
}
|
|
9423
|
+
return getSqlitePrimaryKeys(adapter, table);
|
|
9424
|
+
}
|
|
9425
|
+
async function resolvePrimaryKeys(source, target, table, pkOption) {
|
|
9426
|
+
if (pkOption) {
|
|
9427
|
+
return Array.isArray(pkOption) ? pkOption : [pkOption];
|
|
9428
|
+
}
|
|
9429
|
+
let pks = await detectPrimaryKeys(source, table);
|
|
9430
|
+
if (pks.length === 0) {
|
|
9431
|
+
pks = await detectPrimaryKeys(target, table);
|
|
9432
|
+
}
|
|
9433
|
+
return pks;
|
|
9434
|
+
}
|
|
9389
9435
|
async function syncTransfer(source, target, options, _direction) {
|
|
9390
9436
|
const {
|
|
9391
9437
|
tables,
|
|
9392
9438
|
onProgress,
|
|
9393
9439
|
batchSize = 100,
|
|
9394
9440
|
conflictColumn = "updated_at",
|
|
9395
|
-
primaryKey
|
|
9441
|
+
primaryKey: pkOption
|
|
9396
9442
|
} = options;
|
|
9397
9443
|
const results = [];
|
|
9398
9444
|
for (let i = 0;i < tables.length; i++) {
|
|
@@ -9427,10 +9473,45 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
9427
9473
|
results.push(result);
|
|
9428
9474
|
continue;
|
|
9429
9475
|
}
|
|
9476
|
+
const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
|
|
9430
9477
|
const columns = Object.keys(rows[0]);
|
|
9431
|
-
|
|
9432
|
-
|
|
9433
|
-
|
|
9478
|
+
if (pkColumns.length === 0) {
|
|
9479
|
+
result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
|
|
9480
|
+
onProgress?.({
|
|
9481
|
+
table,
|
|
9482
|
+
phase: "writing",
|
|
9483
|
+
rowsRead: result.rowsRead,
|
|
9484
|
+
rowsWritten: 0,
|
|
9485
|
+
totalTables: tables.length,
|
|
9486
|
+
currentTableIndex: i
|
|
9487
|
+
});
|
|
9488
|
+
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
9489
|
+
const batch = rows.slice(offset, offset + batchSize);
|
|
9490
|
+
try {
|
|
9491
|
+
if (isAsyncAdapter(target)) {
|
|
9492
|
+
await batchInsertPg(target, table, columns, batch);
|
|
9493
|
+
} else {
|
|
9494
|
+
batchInsertSqlite(target, table, columns, batch);
|
|
9495
|
+
}
|
|
9496
|
+
result.rowsWritten += batch.length;
|
|
9497
|
+
} catch (err) {
|
|
9498
|
+
result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
|
|
9499
|
+
}
|
|
9500
|
+
}
|
|
9501
|
+
onProgress?.({
|
|
9502
|
+
table,
|
|
9503
|
+
phase: "done",
|
|
9504
|
+
rowsRead: result.rowsRead,
|
|
9505
|
+
rowsWritten: result.rowsWritten,
|
|
9506
|
+
totalTables: tables.length,
|
|
9507
|
+
currentTableIndex: i
|
|
9508
|
+
});
|
|
9509
|
+
results.push(result);
|
|
9510
|
+
continue;
|
|
9511
|
+
}
|
|
9512
|
+
const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
|
|
9513
|
+
if (missingPks.length > 0) {
|
|
9514
|
+
result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
|
|
9434
9515
|
results.push(result);
|
|
9435
9516
|
continue;
|
|
9436
9517
|
}
|
|
@@ -9442,14 +9523,14 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
9442
9523
|
totalTables: tables.length,
|
|
9443
9524
|
currentTableIndex: i
|
|
9444
9525
|
});
|
|
9445
|
-
const updateCols = columns.filter((c) => c
|
|
9526
|
+
const updateCols = columns.filter((c) => !pkColumns.includes(c));
|
|
9446
9527
|
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
9447
9528
|
const batch = rows.slice(offset, offset + batchSize);
|
|
9448
9529
|
try {
|
|
9449
9530
|
if (isAsyncAdapter(target)) {
|
|
9450
|
-
await batchUpsertPg(target, table, columns, updateCols,
|
|
9531
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
9451
9532
|
} else {
|
|
9452
|
-
batchUpsertSqlite(target, table, columns, updateCols,
|
|
9533
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
9453
9534
|
}
|
|
9454
9535
|
result.rowsWritten += batch.length;
|
|
9455
9536
|
} catch (err) {
|
|
@@ -9479,7 +9560,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
9479
9560
|
}
|
|
9480
9561
|
return results;
|
|
9481
9562
|
}
|
|
9482
|
-
async function batchUpsertPg(target, table, columns, updateCols,
|
|
9563
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
9483
9564
|
if (batch.length === 0)
|
|
9484
9565
|
return;
|
|
9485
9566
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -9487,20 +9568,43 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, bat
|
|
|
9487
9568
|
const offset = rowIdx * columns.length;
|
|
9488
9569
|
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
9489
9570
|
}).join(", ");
|
|
9490
|
-
const
|
|
9571
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
9572
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
9491
9573
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
9492
|
-
ON CONFLICT (
|
|
9574
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
9493
9575
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
9494
9576
|
await target.run(sql, ...params);
|
|
9495
9577
|
}
|
|
9496
|
-
function batchUpsertSqlite(target, table, columns, updateCols,
|
|
9578
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
9497
9579
|
if (batch.length === 0)
|
|
9498
9580
|
return;
|
|
9499
9581
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
9500
9582
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
9501
|
-
const
|
|
9583
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
9584
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
9502
9585
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
9503
|
-
ON CONFLICT (
|
|
9586
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
9587
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
9588
|
+
target.run(sql, ...params);
|
|
9589
|
+
}
|
|
9590
|
+
async function batchInsertPg(target, table, columns, batch) {
|
|
9591
|
+
if (batch.length === 0)
|
|
9592
|
+
return;
|
|
9593
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
9594
|
+
const valuePlaceholders = batch.map((_, rowIdx) => {
|
|
9595
|
+
const offset = rowIdx * columns.length;
|
|
9596
|
+
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
9597
|
+
}).join(", ");
|
|
9598
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
9599
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
9600
|
+
await target.run(sql, ...params);
|
|
9601
|
+
}
|
|
9602
|
+
function batchInsertSqlite(target, table, columns, batch) {
|
|
9603
|
+
if (batch.length === 0)
|
|
9604
|
+
return;
|
|
9605
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
9606
|
+
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
9607
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
9504
9608
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
9505
9609
|
target.run(sql, ...params);
|
|
9506
9610
|
}
|
package/dist/mcp/index.js
CHANGED
|
@@ -24430,6 +24430,9 @@ class SqliteAdapter {
|
|
|
24430
24430
|
exec(sql) {
|
|
24431
24431
|
this.db.exec(sql);
|
|
24432
24432
|
}
|
|
24433
|
+
query(sql) {
|
|
24434
|
+
return this.db.query(sql);
|
|
24435
|
+
}
|
|
24433
24436
|
prepare(sql) {
|
|
24434
24437
|
const stmt = this.db.prepare(sql);
|
|
24435
24438
|
return {
|
|
@@ -24733,13 +24736,56 @@ function heuristicOrder(tables) {
|
|
|
24733
24736
|
});
|
|
24734
24737
|
return sorted;
|
|
24735
24738
|
}
|
|
24739
|
+
function getSqlitePrimaryKeys(adapter, table) {
|
|
24740
|
+
try {
|
|
24741
|
+
const cols = adapter.all(`PRAGMA table_info("${table}")`);
|
|
24742
|
+
const pkCols = cols.filter((c) => c.pk > 0).sort((a, b) => a.pk - b.pk).map((c) => c.name);
|
|
24743
|
+
return pkCols;
|
|
24744
|
+
} catch {
|
|
24745
|
+
return [];
|
|
24746
|
+
}
|
|
24747
|
+
}
|
|
24748
|
+
async function getPgPrimaryKeys(adapter, table) {
|
|
24749
|
+
try {
|
|
24750
|
+
const rows = await adapter.all(`
|
|
24751
|
+
SELECT kcu.column_name, kcu.ordinal_position
|
|
24752
|
+
FROM information_schema.table_constraints tc
|
|
24753
|
+
JOIN information_schema.key_column_usage kcu
|
|
24754
|
+
ON tc.constraint_name = kcu.constraint_name
|
|
24755
|
+
AND tc.table_schema = kcu.table_schema
|
|
24756
|
+
WHERE tc.constraint_type = 'PRIMARY KEY'
|
|
24757
|
+
AND tc.table_schema = 'public'
|
|
24758
|
+
AND tc.table_name = '${table}'
|
|
24759
|
+
ORDER BY kcu.ordinal_position
|
|
24760
|
+
`);
|
|
24761
|
+
return rows.map((r) => r.column_name);
|
|
24762
|
+
} catch {
|
|
24763
|
+
return [];
|
|
24764
|
+
}
|
|
24765
|
+
}
|
|
24766
|
+
async function detectPrimaryKeys(adapter, table) {
|
|
24767
|
+
if (isAsyncAdapter(adapter)) {
|
|
24768
|
+
return getPgPrimaryKeys(adapter, table);
|
|
24769
|
+
}
|
|
24770
|
+
return getSqlitePrimaryKeys(adapter, table);
|
|
24771
|
+
}
|
|
24772
|
+
async function resolvePrimaryKeys(source, target, table, pkOption) {
|
|
24773
|
+
if (pkOption) {
|
|
24774
|
+
return Array.isArray(pkOption) ? pkOption : [pkOption];
|
|
24775
|
+
}
|
|
24776
|
+
let pks = await detectPrimaryKeys(source, table);
|
|
24777
|
+
if (pks.length === 0) {
|
|
24778
|
+
pks = await detectPrimaryKeys(target, table);
|
|
24779
|
+
}
|
|
24780
|
+
return pks;
|
|
24781
|
+
}
|
|
24736
24782
|
async function syncTransfer(source, target, options, _direction) {
|
|
24737
24783
|
const {
|
|
24738
24784
|
tables,
|
|
24739
24785
|
onProgress,
|
|
24740
24786
|
batchSize = 100,
|
|
24741
24787
|
conflictColumn = "updated_at",
|
|
24742
|
-
primaryKey
|
|
24788
|
+
primaryKey: pkOption
|
|
24743
24789
|
} = options;
|
|
24744
24790
|
const results = [];
|
|
24745
24791
|
for (let i = 0;i < tables.length; i++) {
|
|
@@ -24774,10 +24820,45 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
24774
24820
|
results.push(result);
|
|
24775
24821
|
continue;
|
|
24776
24822
|
}
|
|
24823
|
+
const pkColumns = await resolvePrimaryKeys(source, target, table, pkOption);
|
|
24777
24824
|
const columns = Object.keys(rows[0]);
|
|
24778
|
-
|
|
24779
|
-
|
|
24780
|
-
|
|
24825
|
+
if (pkColumns.length === 0) {
|
|
24826
|
+
result.errors.push(`Table "${table}" has no primary key — inserting without conflict handling`);
|
|
24827
|
+
onProgress?.({
|
|
24828
|
+
table,
|
|
24829
|
+
phase: "writing",
|
|
24830
|
+
rowsRead: result.rowsRead,
|
|
24831
|
+
rowsWritten: 0,
|
|
24832
|
+
totalTables: tables.length,
|
|
24833
|
+
currentTableIndex: i
|
|
24834
|
+
});
|
|
24835
|
+
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
24836
|
+
const batch = rows.slice(offset, offset + batchSize);
|
|
24837
|
+
try {
|
|
24838
|
+
if (isAsyncAdapter(target)) {
|
|
24839
|
+
await batchInsertPg(target, table, columns, batch);
|
|
24840
|
+
} else {
|
|
24841
|
+
batchInsertSqlite(target, table, columns, batch);
|
|
24842
|
+
}
|
|
24843
|
+
result.rowsWritten += batch.length;
|
|
24844
|
+
} catch (err) {
|
|
24845
|
+
result.errors.push(`Batch at offset ${offset}: ${err?.message ?? String(err)}`);
|
|
24846
|
+
}
|
|
24847
|
+
}
|
|
24848
|
+
onProgress?.({
|
|
24849
|
+
table,
|
|
24850
|
+
phase: "done",
|
|
24851
|
+
rowsRead: result.rowsRead,
|
|
24852
|
+
rowsWritten: result.rowsWritten,
|
|
24853
|
+
totalTables: tables.length,
|
|
24854
|
+
currentTableIndex: i
|
|
24855
|
+
});
|
|
24856
|
+
results.push(result);
|
|
24857
|
+
continue;
|
|
24858
|
+
}
|
|
24859
|
+
const missingPks = pkColumns.filter((pk) => !columns.includes(pk));
|
|
24860
|
+
if (missingPks.length > 0) {
|
|
24861
|
+
result.errors.push(`Table "${table}" missing PK columns in data: ${missingPks.join(", ")} — skipping`);
|
|
24781
24862
|
results.push(result);
|
|
24782
24863
|
continue;
|
|
24783
24864
|
}
|
|
@@ -24789,14 +24870,14 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
24789
24870
|
totalTables: tables.length,
|
|
24790
24871
|
currentTableIndex: i
|
|
24791
24872
|
});
|
|
24792
|
-
const updateCols = columns.filter((c) => c
|
|
24873
|
+
const updateCols = columns.filter((c) => !pkColumns.includes(c));
|
|
24793
24874
|
for (let offset = 0;offset < rows.length; offset += batchSize) {
|
|
24794
24875
|
const batch = rows.slice(offset, offset + batchSize);
|
|
24795
24876
|
try {
|
|
24796
24877
|
if (isAsyncAdapter(target)) {
|
|
24797
|
-
await batchUpsertPg(target, table, columns, updateCols,
|
|
24878
|
+
await batchUpsertPg(target, table, columns, updateCols, pkColumns, batch);
|
|
24798
24879
|
} else {
|
|
24799
|
-
batchUpsertSqlite(target, table, columns, updateCols,
|
|
24880
|
+
batchUpsertSqlite(target, table, columns, updateCols, pkColumns, batch);
|
|
24800
24881
|
}
|
|
24801
24882
|
result.rowsWritten += batch.length;
|
|
24802
24883
|
} catch (err) {
|
|
@@ -24826,7 +24907,7 @@ async function syncTransfer(source, target, options, _direction) {
|
|
|
24826
24907
|
}
|
|
24827
24908
|
return results;
|
|
24828
24909
|
}
|
|
24829
|
-
async function batchUpsertPg(target, table, columns, updateCols,
|
|
24910
|
+
async function batchUpsertPg(target, table, columns, updateCols, primaryKeys, batch) {
|
|
24830
24911
|
if (batch.length === 0)
|
|
24831
24912
|
return;
|
|
24832
24913
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
@@ -24834,20 +24915,43 @@ async function batchUpsertPg(target, table, columns, updateCols, primaryKey, bat
|
|
|
24834
24915
|
const offset = rowIdx * columns.length;
|
|
24835
24916
|
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
24836
24917
|
}).join(", ");
|
|
24837
|
-
const
|
|
24918
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
24919
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
24838
24920
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
24839
|
-
ON CONFLICT (
|
|
24921
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
24840
24922
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
24841
24923
|
await target.run(sql, ...params);
|
|
24842
24924
|
}
|
|
24843
|
-
function batchUpsertSqlite(target, table, columns, updateCols,
|
|
24925
|
+
function batchUpsertSqlite(target, table, columns, updateCols, primaryKeys, batch) {
|
|
24844
24926
|
if (batch.length === 0)
|
|
24845
24927
|
return;
|
|
24846
24928
|
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
24847
24929
|
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
24848
|
-
const
|
|
24930
|
+
const pkList = primaryKeys.map((c) => `"${c}"`).join(", ");
|
|
24931
|
+
const setClause = updateCols.length > 0 ? updateCols.map((c) => `"${c}" = EXCLUDED."${c}"`).join(", ") : `"${primaryKeys[0]}" = EXCLUDED."${primaryKeys[0]}"`;
|
|
24849
24932
|
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}
|
|
24850
|
-
ON CONFLICT (
|
|
24933
|
+
ON CONFLICT (${pkList}) DO UPDATE SET ${setClause}`;
|
|
24934
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
24935
|
+
target.run(sql, ...params);
|
|
24936
|
+
}
|
|
24937
|
+
async function batchInsertPg(target, table, columns, batch) {
|
|
24938
|
+
if (batch.length === 0)
|
|
24939
|
+
return;
|
|
24940
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
24941
|
+
const valuePlaceholders = batch.map((_, rowIdx) => {
|
|
24942
|
+
const offset = rowIdx * columns.length;
|
|
24943
|
+
return `(${columns.map((_2, colIdx) => `$${offset + colIdx + 1}`).join(", ")})`;
|
|
24944
|
+
}).join(", ");
|
|
24945
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
24946
|
+
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
24947
|
+
await target.run(sql, ...params);
|
|
24948
|
+
}
|
|
24949
|
+
function batchInsertSqlite(target, table, columns, batch) {
|
|
24950
|
+
if (batch.length === 0)
|
|
24951
|
+
return;
|
|
24952
|
+
const colList = columns.map((c) => `"${c}"`).join(", ");
|
|
24953
|
+
const valuePlaceholders = batch.map(() => `(${columns.map(() => "?").join(", ")})`).join(", ");
|
|
24954
|
+
const sql = `INSERT INTO "${table}" (${colList}) VALUES ${valuePlaceholders}`;
|
|
24851
24955
|
const params = batch.flatMap((row) => columns.map((c) => row[c] ?? null));
|
|
24852
24956
|
target.run(sql, ...params);
|
|
24853
24957
|
}
|
|
@@ -25012,6 +25116,9 @@ class SqliteAdapter2 {
|
|
|
25012
25116
|
exec(sql) {
|
|
25013
25117
|
this.db.exec(sql);
|
|
25014
25118
|
}
|
|
25119
|
+
query(sql) {
|
|
25120
|
+
return this.db.query(sql);
|
|
25121
|
+
}
|
|
25015
25122
|
prepare(sql) {
|
|
25016
25123
|
const stmt = this.db.prepare(sql);
|
|
25017
25124
|
return {
|
package/dist/sync.d.ts
CHANGED
|
@@ -18,8 +18,12 @@ export interface SyncOptions {
|
|
|
18
18
|
batchSize?: number;
|
|
19
19
|
/** Conflict resolution column (default: "updated_at"). Newest wins. */
|
|
20
20
|
conflictColumn?: string;
|
|
21
|
-
/**
|
|
22
|
-
|
|
21
|
+
/**
|
|
22
|
+
* Primary key column name(s). Can be a single column string or an array
|
|
23
|
+
* for composite primary keys (default: auto-detected from the database).
|
|
24
|
+
* If not provided and auto-detection fails, falls back to "id".
|
|
25
|
+
*/
|
|
26
|
+
primaryKey?: string | string[];
|
|
23
27
|
}
|
|
24
28
|
export interface SyncResult {
|
|
25
29
|
table: string;
|
package/dist/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kCAAkC;IAClC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAMnD,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,oBAAoB,GAAG,CAAC,QAAQ,EAAE,YAAY,KAAK,IAAI,CAAC;AAEpE,MAAM,WAAW,WAAW;IAC1B,sBAAsB;IACtB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,kCAAkC;IAClC,UAAU,CAAC,EAAE,oBAAoB,CAAC;IAClC,qDAAqD;IACrD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uEAAuE;IACvE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,cAAc,EACtB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAMD;;;GAGG;AACH,wBAAsB,QAAQ,CAC5B,MAAM,EAAE,cAAc,EACtB,KAAK,EAAE,SAAS,EAChB,OAAO,EAAE,WAAW,GACnB,OAAO,CAAC,UAAU,EAAE,CAAC,CAGvB;AAoiBD;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,EAAE,SAAS,GAAG,MAAM,EAAE,CAKxD;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,EAAE,EAAE,cAAc,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAKxE"}
|
package/package.json
CHANGED