git-sqlite-vfs 0.0.1 → 0.0.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 +67 -32
- package/bin/cli.js +53 -0
- package/c/Makefile +31 -16
- package/c/download-sqlite.cjs +17 -0
- package/c/git-merge-sqlitevfs.c +210 -186
- package/c/gitvfs.c +69 -21
- package/c/output/git-merge-sqlitevfs.exe +0 -0
- package/c/output/gitvfs.dll +0 -0
- package/c/output/gitvfs.o +0 -0
- package/c/output/gitvfs_test.exe +0 -0
- package/c/output/main.o +0 -0
- package/c/output/sqlite3.o +0 -0
- package/c/sqlite-autoconf-3450200/INSTALL +370 -0
- package/c/sqlite-autoconf-3450200/Makefile.am +20 -0
- package/c/sqlite-autoconf-3450200/Makefile.fallback +19 -0
- package/c/sqlite-autoconf-3450200/Makefile.in +1050 -0
- package/c/sqlite-autoconf-3450200/Makefile.msc +1069 -0
- package/c/sqlite-autoconf-3450200/README.txt +113 -0
- package/c/sqlite-autoconf-3450200/Replace.cs +223 -0
- package/c/sqlite-autoconf-3450200/aclocal.m4 +10204 -0
- package/c/sqlite-autoconf-3450200/compile +348 -0
- package/c/sqlite-autoconf-3450200/config.guess +1754 -0
- package/c/sqlite-autoconf-3450200/config.sub +1890 -0
- package/c/sqlite-autoconf-3450200/configure +16887 -0
- package/c/sqlite-autoconf-3450200/configure.ac +270 -0
- package/c/sqlite-autoconf-3450200/depcomp +791 -0
- package/c/sqlite-autoconf-3450200/install-sh +541 -0
- package/c/sqlite-autoconf-3450200/ltmain.sh +11251 -0
- package/c/sqlite-autoconf-3450200/missing +215 -0
- package/c/sqlite-autoconf-3450200/shell.c +29659 -0
- package/c/sqlite-autoconf-3450200/sqlite3.1 +161 -0
- package/c/sqlite-autoconf-3450200/sqlite3.c +255811 -0
- package/c/sqlite-autoconf-3450200/sqlite3.h +13357 -0
- package/c/sqlite-autoconf-3450200/sqlite3.pc.in +13 -0
- package/c/sqlite-autoconf-3450200/sqlite3.rc +83 -0
- package/c/sqlite-autoconf-3450200/sqlite3ext.h +719 -0
- package/c/sqlite-autoconf-3450200/sqlite3rc.h +3 -0
- package/c/sqlite-autoconf-3450200/tea/Makefile.in +475 -0
- package/c/sqlite-autoconf-3450200/tea/README +36 -0
- package/c/sqlite-autoconf-3450200/tea/aclocal.m4 +9 -0
- package/c/sqlite-autoconf-3450200/tea/configure +10179 -0
- package/c/sqlite-autoconf-3450200/tea/configure.ac +227 -0
- package/c/sqlite-autoconf-3450200/tea/doc/sqlite3.n +15 -0
- package/c/sqlite-autoconf-3450200/tea/generic/tclsqlite3.c +4080 -0
- package/c/sqlite-autoconf-3450200/tea/license.terms +6 -0
- package/c/sqlite-autoconf-3450200/tea/pkgIndex.tcl.in +10 -0
- package/c/sqlite-autoconf-3450200/tea/tclconfig/install-sh +528 -0
- package/c/sqlite-autoconf-3450200/tea/tclconfig/tcl.m4 +4067 -0
- package/c/sqlite-autoconf-3450200/tea/win/makefile.vc +430 -0
- package/c/sqlite-autoconf-3450200/tea/win/nmakehlp.c +815 -0
- package/c/sqlite-autoconf-3450200/tea/win/rules.vc +711 -0
- package/c/sqlite-autoconf-3450200.tar.gz +0 -0
- package/c/sqlite3.c +255811 -0
- package/c/sqlite3.h +13357 -0
- package/c/sqlite3ext.h +719 -0
- package/downloader.js +63 -0
- package/index.d.ts +14 -0
- package/index.js +103 -51
- package/install.js +13 -49
- package/package.json +22 -3
- package/c/output/git-merge-sqlitevfs +0 -0
- package/c/output/gitvfs.so +0 -0
- package/c/output/gitvfs_test +0 -0
- package/test.js +0 -209
package/downloader.js
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { execSync } from 'node:child_process';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
export async function downloadOrBuild(targetDir) {
|
|
12
|
+
let pkgVersion = '0.0.2';
|
|
13
|
+
try {
|
|
14
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf-8'));
|
|
15
|
+
pkgVersion = pkg.version;
|
|
16
|
+
} catch(e) {}
|
|
17
|
+
|
|
18
|
+
const platform = os.platform();
|
|
19
|
+
const arch = os.arch();
|
|
20
|
+
|
|
21
|
+
const repo = 'fur-tea-laser/git-sqlite-vfs';
|
|
22
|
+
const assetName = `git-sqlite-vfs-${platform}-${arch}.tar.gz`;
|
|
23
|
+
const url = `https://github.com/${repo}/releases/download/v${pkgVersion}/${assetName}`;
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
console.log(`Attempting to download prebuilt binary: ${url}`);
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(targetDir)) {
|
|
29
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const res = await fetch(url);
|
|
33
|
+
if (!res.ok) {
|
|
34
|
+
throw new Error(`Failed to fetch: ${res.status} ${res.statusText}`);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
38
|
+
const buffer = Buffer.from(arrayBuffer);
|
|
39
|
+
|
|
40
|
+
const tarballPath = path.join(targetDir, 'temp.tar.gz');
|
|
41
|
+
fs.writeFileSync(tarballPath, buffer);
|
|
42
|
+
|
|
43
|
+
execSync(`tar -xzf temp.tar.gz`, { cwd: targetDir });
|
|
44
|
+
fs.unlinkSync(tarballPath);
|
|
45
|
+
|
|
46
|
+
console.log('Successfully downloaded and extracted prebuilt binary.');
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.warn(`Download failed: ${err.message}`);
|
|
49
|
+
console.log('Falling back to building from source...');
|
|
50
|
+
try {
|
|
51
|
+
execSync('npm run build', { stdio: 'inherit', cwd: __dirname });
|
|
52
|
+
|
|
53
|
+
const defaultOutDir = path.join(__dirname, 'c', 'output');
|
|
54
|
+
if (path.resolve(defaultOutDir) !== path.resolve(targetDir)) {
|
|
55
|
+
fs.cpSync(defaultOutDir, targetDir, { recursive: true });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
console.log('Successfully built from source.');
|
|
59
|
+
} catch (buildErr) {
|
|
60
|
+
console.error('Failed to build from source.', buildErr.message);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const GITVFS_EXTENSION_PATH: string;
|
|
2
|
+
|
|
3
|
+
export interface BootstrapOptions {
|
|
4
|
+
dir?: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function bootstrapGitVFS(options?: BootstrapOptions): Promise<void>;
|
|
8
|
+
|
|
9
|
+
export interface ConfigureGitOptions {
|
|
10
|
+
repoDir: string;
|
|
11
|
+
vfsDir: string;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function configureGitIntegration(options: ConfigureGitOptions): Promise<void>;
|
package/index.js
CHANGED
|
@@ -1,55 +1,107 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return new Database(dbPath);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Configures the local Git repository with optimized binary thresholds
|
|
36
|
-
* and strictly wires up our custom C engine as a Git Merge Strategy.
|
|
37
|
-
*/
|
|
38
|
-
static setupGit() {
|
|
39
|
-
try {
|
|
40
|
-
// Optimize Git for 4KB binary pages to guarantee xdelta works nicely
|
|
41
|
-
// without prematurely terminating delta compression loops
|
|
42
|
-
execSync('git config core.bigFileThreshold 10m', { stdio: 'ignore' });
|
|
43
|
-
|
|
44
|
-
// Wire up the custom merge strategy driver with absolute paths
|
|
45
|
-
// This natively binds our C executable to Git's conflict resolution pipeline
|
|
46
|
-
const driverPath = path.resolve(__dirname, 'c/output/git-merge-sqlitevfs');
|
|
47
|
-
execSync(`git config merge.sqlite_logical.name "SQLite Logical Merge Driver"`, { stdio: 'ignore' });
|
|
48
|
-
execSync(`git config merge.sqlite_logical.driver "${driverPath} %O %A %B %P"`, { stdio: 'ignore' });
|
|
49
|
-
} catch (err) {
|
|
50
|
-
console.warn("Warning: Could not configure git attributes automatically.", err.message);
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { execSync } from 'node:child_process';
|
|
5
|
+
import fs from 'node:fs';
|
|
6
|
+
import process from 'node:process';
|
|
7
|
+
import { downloadOrBuild } from './downloader.js';
|
|
8
|
+
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
|
|
12
|
+
// Determine the correct extension based on OS
|
|
13
|
+
const platform = os.platform();
|
|
14
|
+
let ext = 'so';
|
|
15
|
+
if (platform === 'darwin') {
|
|
16
|
+
ext = 'dylib';
|
|
17
|
+
} else if (platform === 'win32') {
|
|
18
|
+
ext = 'dll';
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const extensionPath = path.resolve(__dirname, 'c', 'output', `gitvfs.${ext}`);
|
|
22
|
+
|
|
23
|
+
export const GITVFS_EXTENSION_PATH = extensionPath;
|
|
24
|
+
|
|
25
|
+
export async function bootstrapGitVFS(options = {}) {
|
|
26
|
+
if (options.dir) {
|
|
27
|
+
if (typeof Deno !== 'undefined') {
|
|
28
|
+
Deno.env.set('GIT_SQLITE_VFS_DIR', options.dir);
|
|
29
|
+
} else {
|
|
30
|
+
process.env.GIT_SQLITE_VFS_DIR = options.dir;
|
|
51
31
|
}
|
|
52
32
|
}
|
|
33
|
+
|
|
34
|
+
let currentExtPath = extensionPath;
|
|
35
|
+
if (!fs.existsSync(currentExtPath)) {
|
|
36
|
+
const writableDir = path.join(process.cwd(), '.git-sqlite-vfs-bin');
|
|
37
|
+
await downloadOrBuild(writableDir);
|
|
38
|
+
currentExtPath = path.join(writableDir, `gitvfs.${ext}`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Dynamically import libsql so that we load the extension into its isolated native memory space.
|
|
42
|
+
let Database;
|
|
43
|
+
if (typeof Deno !== 'undefined') {
|
|
44
|
+
// Deno environment
|
|
45
|
+
const lib = await import('npm:libsql');
|
|
46
|
+
Database = lib.default || lib.Database || lib;
|
|
47
|
+
} else {
|
|
48
|
+
// Node.js environment
|
|
49
|
+
const lib = await import('libsql');
|
|
50
|
+
Database = lib.default || lib.Database || lib;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const db = new Database(':memory:');
|
|
54
|
+
db.loadExtension(currentExtPath);
|
|
55
|
+
db.close();
|
|
53
56
|
}
|
|
54
57
|
|
|
55
|
-
|
|
58
|
+
export async function configureGitIntegration({ repoDir, vfsDir }) {
|
|
59
|
+
let driverDir = path.resolve(__dirname, 'c', 'output');
|
|
60
|
+
let driverPath = path.join(driverDir, 'git-merge-sqlitevfs');
|
|
61
|
+
if (platform === 'win32' && !fs.existsSync(driverPath) && fs.existsSync(driverPath + '.exe')) {
|
|
62
|
+
driverPath += '.exe';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (!fs.existsSync(driverPath)) {
|
|
66
|
+
driverDir = path.join(process.cwd(), '.git-sqlite-vfs-bin');
|
|
67
|
+
await downloadOrBuild(driverDir);
|
|
68
|
+
driverPath = path.join(driverDir, 'git-merge-sqlitevfs');
|
|
69
|
+
if (platform === 'win32' && !fs.existsSync(driverPath) && fs.existsSync(driverPath + '.exe')) {
|
|
70
|
+
driverPath += '.exe';
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Set the merge driver
|
|
75
|
+
execSync(`git config merge.sqlitevfs.name "SQLite VFS Merge Driver"`, { cwd: repoDir, stdio: 'ignore' });
|
|
76
|
+
execSync(`git config merge.sqlitevfs.driver "${driverPath} %O %A %B %P"`, { cwd: repoDir, stdio: 'ignore' });
|
|
77
|
+
|
|
78
|
+
// Append to .gitattributes
|
|
79
|
+
const gitattributesPath = path.join(repoDir, '.gitattributes');
|
|
80
|
+
const attributeLine = `${vfsDir}/* merge=sqlitevfs\n`;
|
|
81
|
+
|
|
82
|
+
let content = '';
|
|
83
|
+
if (fs.existsSync(gitattributesPath)) {
|
|
84
|
+
content = fs.readFileSync(gitattributesPath, 'utf-8');
|
|
85
|
+
}
|
|
86
|
+
if (!content.includes(attributeLine.trim())) {
|
|
87
|
+
fs.appendFileSync(gitattributesPath, attributeLine);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Create or update .gitignore in the repo root to ignore SQLite transient files
|
|
91
|
+
const gitignorePath = path.join(repoDir, '.gitignore');
|
|
92
|
+
const ignoreLines = [
|
|
93
|
+
`${vfsDir}/*-journal`,
|
|
94
|
+
`${vfsDir}/*-wal`,
|
|
95
|
+
`${vfsDir}/*-shm`
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
let gitignoreContent = '';
|
|
99
|
+
if (fs.existsSync(gitignorePath)) {
|
|
100
|
+
gitignoreContent = fs.readFileSync(gitignorePath, 'utf-8');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const linesToAdd = ignoreLines.filter(line => !gitignoreContent.includes(line));
|
|
104
|
+
if (linesToAdd.length > 0) {
|
|
105
|
+
fs.appendFileSync(gitignorePath, (gitignoreContent.endsWith('\n') || gitignoreContent === '' ? '' : '\n') + linesToAdd.join('\n') + '\n');
|
|
106
|
+
}
|
|
107
|
+
}
|
package/install.js
CHANGED
|
@@ -1,53 +1,17 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
const pkg = require('./package.json');
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import process from 'node:process';
|
|
4
|
+
import { downloadOrBuild } from './downloader.js';
|
|
6
5
|
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const PLATFORM = os.platform();
|
|
10
|
-
const ARCH = os.arch();
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
11
8
|
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const OUT_DIR = path.join(__dirname, 'c', 'output');
|
|
17
|
-
|
|
18
|
-
function buildFromSource() {
|
|
19
|
-
console.log('Building from source as fallback...');
|
|
20
|
-
try {
|
|
21
|
-
execSync('npm run build', { stdio: 'inherit', cwd: __dirname });
|
|
22
|
-
console.log('Successfully built from source.');
|
|
23
|
-
} catch (e) {
|
|
24
|
-
console.error('Failed to build from source.', e.message);
|
|
25
|
-
process.exit(1);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function downloadAndExtract() {
|
|
30
|
-
// If the SKIP_DOWNLOAD env var is set, or if we are building locally from the repo root
|
|
31
|
-
// we should just build from source.
|
|
32
|
-
if (process.env.SKIP_DOWNLOAD || !fs.existsSync(path.join(__dirname, 'node_modules'))) {
|
|
33
|
-
return buildFromSource();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
console.log(`Attempting to download prebuilt binary: ${DOWNLOAD_URL}`);
|
|
37
|
-
|
|
38
|
-
try {
|
|
39
|
-
if (!fs.existsSync(OUT_DIR)) {
|
|
40
|
-
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Use native curl and tar to download and extract without requiring NPM dependencies.
|
|
44
|
-
// This is supported out-of-the-box on modern Linux, macOS, and Windows 10+
|
|
45
|
-
execSync(`curl -sLf ${DOWNLOAD_URL} | tar -xz -C "${OUT_DIR}"`, { stdio: 'inherit' });
|
|
46
|
-
console.log('Prebuilt binary successfully downloaded and extracted!');
|
|
47
|
-
} catch (err) {
|
|
48
|
-
console.log('Prebuilt binary not found or download failed. Falling back to source compilation...');
|
|
49
|
-
buildFromSource();
|
|
50
|
-
}
|
|
9
|
+
async function run() {
|
|
10
|
+
const targetDir = path.join(__dirname, 'c', 'output');
|
|
11
|
+
await downloadOrBuild(targetDir);
|
|
51
12
|
}
|
|
52
13
|
|
|
53
|
-
|
|
14
|
+
run().catch(err => {
|
|
15
|
+
console.error(err);
|
|
16
|
+
process.exit(1);
|
|
17
|
+
});
|
package/package.json
CHANGED
|
@@ -1,15 +1,34 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "git-sqlite-vfs",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.6",
|
|
4
4
|
"description": "A Git-Versioned SQLite Database via a Custom Virtual File System (VFS)",
|
|
5
5
|
"main": "index.js",
|
|
6
|
+
"types": "index.d.ts",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"bin": {
|
|
9
|
+
"git-sqlite-setup": "bin/cli.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"c/",
|
|
14
|
+
"index.js",
|
|
15
|
+
"index.d.ts",
|
|
16
|
+
"downloader.js",
|
|
17
|
+
"install.js"
|
|
18
|
+
],
|
|
6
19
|
"scripts": {
|
|
7
20
|
"build": "cd c && make",
|
|
8
21
|
"postinstall": "node install.js",
|
|
9
|
-
"pretest": "npm run build && rm -rf .db .git && git init --initial-branch=master",
|
|
10
|
-
"test": "node --test test.js"
|
|
22
|
+
"pretest": "npm run build && rm -rf .db test.db .test-db .compaction-db .git && git init --initial-branch=master",
|
|
23
|
+
"test": "node --test test.node.js test.compaction.node.js test.e2e.node.js",
|
|
24
|
+
"test:deno": "npm run build && rm -rf .db test.db .test-db .git && git init --initial-branch=master && deno test -A test.deno.ts"
|
|
11
25
|
},
|
|
12
26
|
"dependencies": {
|
|
27
|
+
"@libsql/client": "^0.14.0",
|
|
28
|
+
"drizzle-orm": "^0.33.0",
|
|
29
|
+
"libsql": "^0.4.5"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
13
32
|
"better-sqlite3": "^9.4.3"
|
|
14
33
|
},
|
|
15
34
|
"author": "",
|
|
Binary file
|
package/c/output/gitvfs.so
DELETED
|
Binary file
|
package/c/output/gitvfs_test
DELETED
|
Binary file
|
package/test.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
const { describe, it, before } = require('node:test');
|
|
2
|
-
const assert = require('node:assert');
|
|
3
|
-
const { execSync } = require('child_process');
|
|
4
|
-
const fs = require('fs');
|
|
5
|
-
const path = require('path');
|
|
6
|
-
const GitSQLite = require('./index.js');
|
|
7
|
-
|
|
8
|
-
// Helper to run git commands synchronously
|
|
9
|
-
const runGit = (cmd) => execSync(cmd, { stdio: 'pipe' }).toString().trim();
|
|
10
|
-
|
|
11
|
-
// Helper to recursively calculate total directory size
|
|
12
|
-
const getDirSize = (dirPath) => {
|
|
13
|
-
let total = 0;
|
|
14
|
-
if (!fs.existsSync(dirPath)) return 0;
|
|
15
|
-
|
|
16
|
-
const files = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
17
|
-
for (const file of files) {
|
|
18
|
-
const fullPath = path.join(dirPath, file.name);
|
|
19
|
-
if (file.isDirectory()) {
|
|
20
|
-
total += getDirSize(fullPath);
|
|
21
|
-
} else {
|
|
22
|
-
total += fs.statSync(fullPath).size;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return total;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
describe('GitSQLite Architecture Validation', () => {
|
|
29
|
-
let db;
|
|
30
|
-
|
|
31
|
-
before(() => {
|
|
32
|
-
// Initialize Git repo optimizations and register driver configurations
|
|
33
|
-
GitSQLite.setupGit();
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('Test 1: Initialization & VFS Sharding', () => {
|
|
37
|
-
db = GitSQLite.open('.db');
|
|
38
|
-
|
|
39
|
-
// Create initial schema
|
|
40
|
-
db.exec(`
|
|
41
|
-
CREATE TABLE test_data (id INTEGER PRIMARY KEY, name TEXT, value BLOB);
|
|
42
|
-
CREATE TABLE test_settings (config_key TEXT PRIMARY KEY, config_val TEXT);
|
|
43
|
-
`);
|
|
44
|
-
|
|
45
|
-
// Insert initial baseline data using parameterized transactions
|
|
46
|
-
const insertData = db.prepare("INSERT INTO test_data (name, value) VALUES (?, randomblob(100))");
|
|
47
|
-
const insertSettings = db.prepare("INSERT INTO test_settings (config_key, config_val) VALUES (?, ?)");
|
|
48
|
-
|
|
49
|
-
db.exec('BEGIN TRANSACTION;');
|
|
50
|
-
for (let i = 1; i <= 50; i++) {
|
|
51
|
-
insertData.run(`Initial ${i}`);
|
|
52
|
-
}
|
|
53
|
-
insertSettings.run('theme', 'dark');
|
|
54
|
-
db.exec('COMMIT;');
|
|
55
|
-
|
|
56
|
-
// Close to flush SQLite connections (VFS sync)
|
|
57
|
-
db.close();
|
|
58
|
-
|
|
59
|
-
// Assert our C VFS dynamically intercepted physical I/O and sharded the B-Tree!
|
|
60
|
-
assert.ok(fs.existsSync('.db/pages'), 'Pages directory should exist');
|
|
61
|
-
assert.ok(fs.existsSync('.db/pages/size.meta'), 'size.meta persistence state should exist');
|
|
62
|
-
|
|
63
|
-
// Verify git tracking structure
|
|
64
|
-
assert.ok(fs.existsSync('.db/.gitignore'), '.gitignore should be generated');
|
|
65
|
-
assert.ok(fs.existsSync('.db/pages/.gitattributes'), '.gitattributes should be generated');
|
|
66
|
-
|
|
67
|
-
// Stage and Commit Snapshot 1
|
|
68
|
-
runGit('git add -A -f .db/');
|
|
69
|
-
runGit('git commit -m "Snapshot 1: Initial DB state"');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('Test 2: VACUUM and Physical B-Tree Shrinkage', () => {
|
|
73
|
-
db = GitSQLite.open('.db');
|
|
74
|
-
|
|
75
|
-
// Insert a massive amount of rows to forcibly expand the B-Tree footprint
|
|
76
|
-
db.exec('BEGIN TRANSACTION;');
|
|
77
|
-
const insertData = db.prepare("INSERT INTO test_data (name, value) VALUES ('Bulk', randomblob(100))");
|
|
78
|
-
for (let i = 0; i < 10000; i++) {
|
|
79
|
-
insertData.run();
|
|
80
|
-
}
|
|
81
|
-
db.exec('COMMIT;');
|
|
82
|
-
db.close();
|
|
83
|
-
|
|
84
|
-
// Capture total file system footprint of the expanded sharded VFS
|
|
85
|
-
const sizeBefore = getDirSize('.db/pages');
|
|
86
|
-
|
|
87
|
-
// Reopen, execute a massive deletion, and trigger a vacuum
|
|
88
|
-
db = GitSQLite.open('.db');
|
|
89
|
-
db.exec("DELETE FROM test_data WHERE id > 50;");
|
|
90
|
-
db.exec("VACUUM;");
|
|
91
|
-
db.close();
|
|
92
|
-
|
|
93
|
-
// Capture total file system footprint post-vacuum
|
|
94
|
-
const sizeAfter = getDirSize('.db/pages');
|
|
95
|
-
|
|
96
|
-
// Assert mathematical shrinkage: our xTruncate implementation successfully unlinked dead pages!
|
|
97
|
-
assert.ok(sizeAfter < sizeBefore, `Directory size must shrink after VACUUM. Before: ${sizeBefore}, After: ${sizeAfter}`);
|
|
98
|
-
|
|
99
|
-
// Stage and Commit Snapshot 2 (Unlinked files must be natively staged as Git deletions)
|
|
100
|
-
runGit('git add -A -f .db/');
|
|
101
|
-
runGit('git commit -m "Snapshot 2: Vacuumed database"');
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it('Test 3: Git Branching & True 3-Way Merge (DDL + Row Conflicts)', () => {
|
|
105
|
-
// --- CONFLICT BRANCH MUTATIONS ---
|
|
106
|
-
runGit('git checkout -b conflict_branch');
|
|
107
|
-
db = GitSQLite.open('.db');
|
|
108
|
-
db.exec(`
|
|
109
|
-
INSERT INTO test_data (id, name, value) VALUES (10001, 'conflict_branch', randomblob(100));
|
|
110
|
-
INSERT INTO test_settings (config_key, config_val) VALUES ('plugin', 'enabled');
|
|
111
|
-
UPDATE test_data SET name = 'branch_update' WHERE id = 10;
|
|
112
|
-
DELETE FROM test_data WHERE id = 15;
|
|
113
|
-
UPDATE test_data SET name = 'branch_wins' WHERE id = 50;
|
|
114
|
-
CREATE TABLE new_feature (id INTEGER PRIMARY KEY, feature_name TEXT);
|
|
115
|
-
INSERT INTO new_feature (id, feature_name) VALUES (1, 'version_control');
|
|
116
|
-
CREATE INDEX idx_test_name ON test_data(name);
|
|
117
|
-
DROP TABLE test_settings;
|
|
118
|
-
`);
|
|
119
|
-
db.close();
|
|
120
|
-
runGit('git add -A -f .db/');
|
|
121
|
-
runGit('git commit -m "conflict_branch: Schema evolution and row updates"');
|
|
122
|
-
|
|
123
|
-
// --- MASTER BRANCH MUTATIONS ---
|
|
124
|
-
runGit('git checkout master');
|
|
125
|
-
db = GitSQLite.open('.db');
|
|
126
|
-
db.exec(`
|
|
127
|
-
INSERT INTO test_data (id, name, value) VALUES (10002, 'master', randomblob(100));
|
|
128
|
-
CREATE TABLE unrelated_table (id INTEGER);
|
|
129
|
-
UPDATE test_data SET name = 'master_update' WHERE id = 20;
|
|
130
|
-
UPDATE test_data SET name = 'master_wins' WHERE id = 50;
|
|
131
|
-
`);
|
|
132
|
-
db.close();
|
|
133
|
-
runGit('git add -A -f .db/');
|
|
134
|
-
runGit('git commit -m "master: Insertions and row updates"');
|
|
135
|
-
|
|
136
|
-
// --- THE CUSTOM GIT MERGE STRATEGY ---
|
|
137
|
-
const binPath = path.resolve(__dirname, 'c/output');
|
|
138
|
-
try {
|
|
139
|
-
// By utilizing the -s sqlitevfs strategy and augmenting our PATH, Git natively
|
|
140
|
-
// delegates the entire branch resolution to our SQLite C engine!
|
|
141
|
-
execSync(`PATH=$PATH:${binPath} git merge -s sqlitevfs conflict_branch -m "Merge conflict_branch into master"`, { stdio: 'pipe' });
|
|
142
|
-
} catch (e) {
|
|
143
|
-
console.error("Merge failed:\n", e.stdout?.toString(), e.stderr?.toString());
|
|
144
|
-
throw e;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// --- ASSERTIONS (Mathematical verification of 3-Way Logical Merge) ---
|
|
148
|
-
db = GitSQLite.open('.db');
|
|
149
|
-
|
|
150
|
-
// Assert True Row Conflict Resolution (Master Wins)
|
|
151
|
-
const row50 = db.prepare("SELECT name FROM test_data WHERE id = 50").get();
|
|
152
|
-
assert.strictEqual(row50.name, 'master_wins', 'Master must win true row-level collisions by Custom Merge Strategy logic');
|
|
153
|
-
|
|
154
|
-
// Assert standard branch updates
|
|
155
|
-
const row10 = db.prepare("SELECT name FROM test_data WHERE id = 10").get();
|
|
156
|
-
assert.strictEqual(row10.name, 'branch_update');
|
|
157
|
-
|
|
158
|
-
const row20 = db.prepare("SELECT name FROM test_data WHERE id = 20").get();
|
|
159
|
-
assert.strictEqual(row20.name, 'master_update');
|
|
160
|
-
|
|
161
|
-
// Assert branch deletions
|
|
162
|
-
const row15 = db.prepare("SELECT name FROM test_data WHERE id = 15").get();
|
|
163
|
-
assert.strictEqual(row15, undefined, 'Row 15 must have been deleted by conflict_branch');
|
|
164
|
-
|
|
165
|
-
// Assert 3-Way Schema Evolution (DDL Merge)
|
|
166
|
-
const settingsTable = db.prepare("SELECT count(*) as cnt FROM sqlite_schema WHERE name='test_settings'").get();
|
|
167
|
-
assert.strictEqual(settingsTable.cnt, 0, 'test_settings table must be mathematically DROPPED');
|
|
168
|
-
|
|
169
|
-
const newFeatureRow = db.prepare("SELECT feature_name FROM new_feature WHERE id = 1").get();
|
|
170
|
-
assert.strictEqual(newFeatureRow.feature_name, 'version_control', 'new_feature table and its row data must exist');
|
|
171
|
-
|
|
172
|
-
const idx = db.prepare("SELECT name FROM sqlite_schema WHERE type='index' AND name='idx_test_name'").get();
|
|
173
|
-
assert.ok(idx, 'idx_test_name index must have been created');
|
|
174
|
-
|
|
175
|
-
db.close();
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('Test 4: Time Travel (Disaster Recovery via Git)', () => {
|
|
179
|
-
db = GitSQLite.open('.db');
|
|
180
|
-
|
|
181
|
-
// Assert initial baseline existence
|
|
182
|
-
let featureTable = db.prepare("SELECT count(*) as cnt FROM sqlite_schema WHERE name='new_feature'").get();
|
|
183
|
-
assert.strictEqual(featureTable.cnt, 1);
|
|
184
|
-
|
|
185
|
-
// Execute a catastrophic, destructive operation
|
|
186
|
-
db.exec("DROP TABLE new_feature;");
|
|
187
|
-
featureTable = db.prepare("SELECT count(*) as cnt FROM sqlite_schema WHERE name='new_feature'").get();
|
|
188
|
-
assert.strictEqual(featureTable.cnt, 0, 'Table must be completely dropped from SQLite');
|
|
189
|
-
db.close();
|
|
190
|
-
|
|
191
|
-
// Commit the disaster
|
|
192
|
-
runGit('git add -A -f .db/');
|
|
193
|
-
runGit('git commit -m "Oops, accidentally dropped new_feature"');
|
|
194
|
-
|
|
195
|
-
// Initiate Time Travel (Git Reset)
|
|
196
|
-
// Because the database is perfectly versioned, Git instantly restores the .bin files
|
|
197
|
-
runGit('git reset --hard HEAD~1');
|
|
198
|
-
|
|
199
|
-
// Reopen DB and Verify absolute recovery
|
|
200
|
-
db = GitSQLite.open('.db');
|
|
201
|
-
featureTable = db.prepare("SELECT count(*) as cnt FROM sqlite_schema WHERE name='new_feature'").get();
|
|
202
|
-
assert.strictEqual(featureTable.cnt, 1, 'Table schema must be fully resurrected natively by Git!');
|
|
203
|
-
|
|
204
|
-
const row = db.prepare("SELECT feature_name FROM new_feature WHERE id = 1").get();
|
|
205
|
-
assert.strictEqual(row.feature_name, 'version_control', 'Physical row data must be fully intact after time travel!');
|
|
206
|
-
|
|
207
|
-
db.close();
|
|
208
|
-
});
|
|
209
|
-
});
|