Skip to content

Commit 901a010

Browse files
Update changes
1 parent 5a7a321 commit 901a010

8 files changed

Lines changed: 796 additions & 14 deletions

File tree

packages/dart_node_coverage/bin/coverage.dart

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,8 @@ Future<Result<void, String>> _instrumentFileInPlace(String filePath) async {
170170
final parseResult = parseExecutableLines(source, filePath);
171171
switch (parseResult) {
172172
case Success(:final value):
173+
final relativePath = p.relative(filePath);
174+
stdout.writeln(' $relativePath: ${value.length} executable lines');
173175
final instrumentResult = instrumentSource(
174176
sourceCode: source,
175177
filePath: filePath,
@@ -210,19 +212,19 @@ Future<Result<void, String>> _runTests(String packageDir) async {
210212
? {'NODE_PATH': p.join(packageDir, 'node_modules')}
211213
: <String, String>{};
212214

213-
final result = await Process.run(
215+
final process = await Process.start(
214216
'dart',
215217
testArgs,
216218
workingDirectory: packageDir,
217219
environment: environment,
220+
mode: ProcessStartMode.inheritStdio,
218221
);
219222

220-
stdout.writeln('Test stdout: ${result.stdout}');
221-
stderr.writeln('Test stderr: ${result.stderr}');
223+
final exitCode = await process.exitCode;
222224

223-
return result.exitCode == 0
225+
return exitCode == 0
224226
? const Success<void, String>(null)
225-
: Error<void, String>('Tests failed with exit code ${result.exitCode}');
227+
: Error<void, String>('Tests failed with exit code $exitCode');
226228
}
227229

228230
Result<void, String> _generateLcov(String packageDir, String outputPath) {

packages/dart_node_coverage/lib/src/instrumenter.dart

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,24 +62,31 @@ Result<String, String> instrumentSource({
6262
}
6363

6464
/// Find the index where the coverage import should be inserted.
65-
/// After library directive if present, otherwise after existing imports,
66-
/// or at the top if no imports.
65+
/// After the last import/export directive (including multi-line ones),
66+
/// after library directive, or at the top.
6767
int _findImportInsertIndex(List<String> lines) {
68-
var lastImportIndex = -1;
68+
var lastDirectiveEnd = -1;
6969
var libraryIndex = -1;
70+
var inDirective = false;
7071

7172
for (var i = 0; i < lines.length; i++) {
7273
final trimmed = lines[i].trim();
7374
if (trimmed.startsWith('library')) libraryIndex = i;
74-
if (trimmed.startsWith('import ')) lastImportIndex = i;
75+
final startsDirective =
76+
trimmed.startsWith('import ') || trimmed.startsWith('export ');
77+
if (startsDirective) inDirective = true;
78+
if (inDirective && trimmed.endsWith(';')) {
79+
lastDirectiveEnd = i;
80+
inDirective = false;
81+
}
7582
}
7683

77-
// Insert after last import, or after library, or at top
78-
return lastImportIndex >= 0
79-
? lastImportIndex + 1
84+
// Insert after last directive end, or after library, or at top
85+
return lastDirectiveEnd >= 0
86+
? lastDirectiveEnd + 1
8087
: libraryIndex >= 0
81-
? libraryIndex + 1
82-
: 0;
88+
? libraryIndex + 1
89+
: 0;
8390
}
8491

8592
/// Inject coverage probe at the start of a line.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
/// Typed Dart bindings for sql.js (SQLite compiled to WebAssembly).
2+
///
3+
/// Provides synchronous SQLite3 access via WebAssembly.
4+
/// Call [initializeSqlJs] once at startup, then use [openDatabase].
5+
library;
6+
7+
export 'src/database.dart';
8+
export 'src/statement.dart';
9+
export 'src/types.dart';
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
/// Database bindings for sql.js (SQLite compiled to WebAssembly).
2+
library;
3+
4+
import 'dart:js_interop';
5+
import 'dart:js_interop_unsafe';
6+
7+
import 'package:dart_node_core/dart_node_core.dart';
8+
import 'package:dart_node_sql_js/src/statement.dart';
9+
import 'package:nadz/nadz.dart';
10+
11+
/// Pre-initialized sql.js runtime.
12+
///
13+
/// Obtained from [initializeSqlJs], passed to [openDatabase].
14+
typedef SqlJsRuntime = ({JSFunction databaseConstructor});
15+
16+
/// Initialize sql.js. Call once at startup.
17+
///
18+
/// Returns a [SqlJsRuntime] that must be passed to [openDatabase].
19+
Future<Result<SqlJsRuntime, String>> initializeSqlJs() async {
20+
try {
21+
final initFn = requireModule('sql.js') as JSFunction;
22+
final promise = initFn.callAsFunction(null) as JSPromise<JSAny?>;
23+
final sqlJs = await promise.toDart as JSObject;
24+
final dbConstructor = sqlJs['Database'] as JSFunction;
25+
return Success((databaseConstructor: dbConstructor));
26+
} catch (e) {
27+
return Error('Failed to initialize sql.js: $e');
28+
}
29+
}
30+
31+
/// A sql.js database connection.
32+
typedef Database = ({
33+
/// Prepare a SQL statement.
34+
Result<Statement, String> Function(String sql) prepare,
35+
36+
/// Execute raw SQL (no results).
37+
Result<void, String> Function(String sql) exec,
38+
39+
/// Close the database.
40+
Result<void, String> Function() close,
41+
42+
/// Set a pragma value.
43+
Result<void, String> Function(String pragmaValue) pragma,
44+
45+
/// Check if database is open.
46+
bool Function() isOpen,
47+
});
48+
49+
/// Open a sql.js database.
50+
///
51+
/// If [path] points to an existing file, loads it.
52+
/// Otherwise creates a new empty database.
53+
/// Auto-persists to disk after write operations.
54+
Result<Database, String> openDatabase(
55+
String path, {
56+
required SqlJsRuntime sqlJs,
57+
}) {
58+
try {
59+
final fs = requireModule('fs') as JSObject;
60+
final existsSyncFn = fs['existsSync'] as JSFunction;
61+
final readFileSyncFn = fs['readFileSync'] as JSFunction;
62+
63+
JSObject jsDb;
64+
final fileExists =
65+
(existsSyncFn.callAsFunction(fs, path.toJS) as JSBoolean).toDart;
66+
67+
if (fileExists) {
68+
final buffer = readFileSyncFn.callAsFunction(fs, path.toJS);
69+
jsDb = sqlJs.databaseConstructor
70+
.callAsConstructor<JSObject>(buffer);
71+
} else {
72+
jsDb = sqlJs.databaseConstructor.callAsConstructor<JSObject>();
73+
}
74+
75+
// sql.js is in-memory; WAL and busy_timeout do not apply.
76+
// Enable foreign keys for referential integrity.
77+
_dbRun(jsDb, 'PRAGMA foreign_keys = ON');
78+
79+
return Success(_createDatabase(jsDb, path, fs));
80+
} catch (e) {
81+
return Error('Failed to open database: $e');
82+
}
83+
}
84+
85+
/// Run a SQL statement directly on the JS database object.
86+
void _dbRun(JSObject jsDb, String sql) {
87+
(jsDb['run'] as JSFunction).callAsFunction(jsDb, sql.toJS);
88+
}
89+
90+
/// Persist the in-memory database to disk.
91+
void _save(JSObject jsDb, String path, JSObject fs) {
92+
final exportFn = jsDb['export'] as JSFunction;
93+
final data = exportFn.callAsFunction(jsDb);
94+
95+
final bufferClass = requireModule('buffer') as JSObject;
96+
final bufferFrom =
97+
(bufferClass['Buffer'] as JSObject)['from'] as JSFunction;
98+
final nodeBuffer = bufferFrom.callAsFunction(null, data);
99+
100+
final writeFileSyncFn = fs['writeFileSync'] as JSFunction;
101+
writeFileSyncFn.callAsFunction(fs, path.toJS, nodeBuffer);
102+
}
103+
104+
Database _createDatabase(JSObject jsDb, String path, JSObject fs) {
105+
var open = true;
106+
107+
return (
108+
prepare: (sql) => _dbPrepare(jsDb, sql, path, fs),
109+
exec: (sql) => _dbExec(jsDb, sql, path, fs),
110+
close: () => _dbClose(jsDb, path, fs, () => open = false),
111+
pragma: (pragmaValue) => _dbPragma(jsDb, pragmaValue),
112+
isOpen: () => open,
113+
);
114+
}
115+
116+
Result<Statement, String> _dbPrepare(
117+
JSObject jsDb,
118+
String sql,
119+
String path,
120+
JSObject fs,
121+
) {
122+
try {
123+
final prepareFn = jsDb['prepare'] as JSFunction;
124+
final jsStmt =
125+
prepareFn.callAsFunction(jsDb, sql.toJS) as JSObject;
126+
return Success(
127+
createStatement(jsStmt, jsDb, onWrite: () => _save(jsDb, path, fs)),
128+
);
129+
} catch (e) {
130+
return Error('Failed to prepare statement: $e');
131+
}
132+
}
133+
134+
Result<void, String> _dbExec(
135+
JSObject jsDb,
136+
String sql,
137+
String path,
138+
JSObject fs,
139+
) {
140+
try {
141+
// sql.js exec() handles multiple statements separated by ;
142+
(jsDb['exec'] as JSFunction).callAsFunction(jsDb, sql.toJS);
143+
_save(jsDb, path, fs);
144+
return const Success(null);
145+
} catch (e) {
146+
return Error('Failed to exec: $e');
147+
}
148+
}
149+
150+
Result<void, String> _dbClose(
151+
JSObject jsDb,
152+
String path,
153+
JSObject fs,
154+
void Function() markClosed,
155+
) {
156+
try {
157+
_save(jsDb, path, fs);
158+
(jsDb['close'] as JSFunction).callAsFunction(jsDb);
159+
markClosed();
160+
return const Success(null);
161+
} catch (e) {
162+
return Error('Failed to close database: $e');
163+
}
164+
}
165+
166+
Result<void, String> _dbPragma(JSObject jsDb, String pragmaValue) {
167+
try {
168+
_dbRun(jsDb, 'PRAGMA $pragmaValue');
169+
return const Success(null);
170+
} catch (e) {
171+
return Error('Failed to set pragma: $e');
172+
}
173+
}

0 commit comments

Comments
 (0)