datasette-ts 0.0.2 → 0.0.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "datasette-ts",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/build.mjs CHANGED
@@ -23,6 +23,14 @@ await build({
23
23
  platform: "node",
24
24
  format: "esm",
25
25
  target: ["node24"],
26
+ banner: {
27
+ js: [
28
+ "#!/usr/bin/env node",
29
+ 'import { createRequire } from "node:module";',
30
+ "const require = createRequire(import.meta.url);",
31
+ "",
32
+ ].join("\n"),
33
+ },
26
34
  external: sharedExternals,
27
35
  });
28
36
 
@@ -1,8 +1,11 @@
1
- import { execFileSync } from "node:child_process";
1
+ import { spawn } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
- import { createReadStream } from "node:fs";
4
- import { mkdir, stat, writeFile } from "node:fs/promises";
3
+ import { createReadStream, createWriteStream } from "node:fs";
4
+ import { mkdir, stat, unlink } from "node:fs/promises";
5
+ import { once } from "node:events";
5
6
  import path from "node:path";
7
+ import { createInterface } from "node:readline";
8
+ import { pipeline } from "node:stream/promises";
6
9
  import { pathToFileURL } from "node:url";
7
10
  import { createClient } from "@libsql/client";
8
11
 
@@ -10,13 +13,15 @@ export async function dumpSqliteForD1(options) {
10
13
  const baseName =
11
14
  options.outputName ?? path.basename(options.dbFile, path.extname(options.dbFile));
12
15
  const outputPath = path.join(options.outputDir, `${baseName}.sql`);
16
+ const rawPath = path.join(options.outputDir, `${baseName}.raw.sql`);
13
17
 
14
18
  await mkdir(options.outputDir, { recursive: true });
15
- const rawDump = execFileSync("sqlite3", [options.dbFile, ".dump"], {
16
- encoding: "utf8",
17
- });
18
- const cleaned = normalizeDump(rawDump);
19
- await writeFile(outputPath, cleaned);
19
+ await dumpSqliteToFile(options.dbFile, rawPath);
20
+ try {
21
+ await normalizeDumpFile(rawPath, outputPath);
22
+ } finally {
23
+ await unlink(rawPath).catch(() => undefined);
24
+ }
20
25
 
21
26
  return outputPath;
22
27
  }
@@ -158,48 +163,90 @@ function escapeIdentifier(name) {
158
163
  return `"${escaped}"`;
159
164
  }
160
165
 
161
- function normalizeDump(rawDump) {
162
- const lines = rawDump.split("\n");
163
- const output = [];
166
+ async function normalizeDumpFile(inputPath, outputPath) {
164
167
  const tablesInOrder = [];
165
168
  const viewsInOrder = [];
166
- for (const line of lines) {
169
+ await forEachLine(inputPath, (line) => {
167
170
  const tableMatch = line.match(/^CREATE TABLE\s+("?[^"]+"?)/i);
168
171
  if (tableMatch) {
169
172
  tablesInOrder.push(tableMatch[1].replace(/\s*\($/, ""));
170
- continue;
173
+ return;
171
174
  }
172
175
  const viewMatch = line.match(/^CREATE VIEW\s+("?[^"]+"?)/i);
173
176
  if (viewMatch) {
174
177
  viewsInOrder.push(viewMatch[1].replace(/\s*\($/, ""));
175
178
  }
176
- }
179
+ });
180
+
181
+ const outputStream = createWriteStream(outputPath, { encoding: "utf8" });
177
182
  for (const viewName of viewsInOrder.reverse()) {
178
- output.push(`DROP VIEW IF EXISTS ${viewName};`);
183
+ await writeLine(outputStream, `DROP VIEW IF EXISTS ${viewName};`);
179
184
  }
180
185
  for (const tableName of tablesInOrder.reverse()) {
181
- output.push(`DROP TABLE IF EXISTS ${tableName};`);
186
+ await writeLine(outputStream, `DROP TABLE IF EXISTS ${tableName};`);
182
187
  }
183
- for (const line of lines) {
188
+ await forEachLine(inputPath, async (line) => {
184
189
  if (line === "BEGIN TRANSACTION;" || line === "COMMIT;") {
185
- continue;
190
+ return;
186
191
  }
187
192
  if (line.startsWith("PRAGMA foreign_keys=")) {
188
- continue;
189
- }
190
- const tableMatch = line.match(/^CREATE TABLE\s+("?[^"]+"?)/i);
191
- if (tableMatch) {
192
- output.push(line);
193
- continue;
193
+ return;
194
194
  }
195
- const viewMatch = line.match(/^CREATE VIEW\s+("?[^"]+"?)/i);
196
- if (viewMatch) {
197
- output.push(line);
198
- continue;
195
+ await writeLine(outputStream, line);
196
+ });
197
+ outputStream.end();
198
+ await once(outputStream, "finish");
199
+ }
200
+
201
+ async function dumpSqliteToFile(dbFile, outputPath) {
202
+ const child = spawn("sqlite3", [dbFile, ".dump"], {
203
+ stdio: ["ignore", "pipe", "pipe"],
204
+ });
205
+ let stderr = "";
206
+ if (child.stderr) {
207
+ child.stderr.setEncoding("utf8");
208
+ child.stderr.on("data", (chunk) => {
209
+ stderr += chunk;
210
+ });
211
+ }
212
+ const stdout = child.stdout;
213
+ if (!stdout) {
214
+ const [error] = await once(child, "error");
215
+ throw error ?? new Error("sqlite3 stdout is unavailable.");
216
+ }
217
+
218
+ const outputStream = createWriteStream(outputPath, { encoding: "utf8" });
219
+ child.once("error", (error) => {
220
+ stdout.destroy(error);
221
+ outputStream.destroy(error);
222
+ });
223
+
224
+ await pipeline(stdout, outputStream);
225
+ const [code, signal] = await once(child, "close");
226
+ if (code !== 0) {
227
+ const suffix = signal ? ` (signal ${signal})` : "";
228
+ const message = stderr.trim() || `sqlite3 exited with code ${code ?? "unknown"}${suffix}`;
229
+ throw new Error(message);
230
+ }
231
+ }
232
+
233
+ async function forEachLine(filePath, handler) {
234
+ const stream = createReadStream(filePath, { encoding: "utf8" });
235
+ const rl = createInterface({ input: stream, crlfDelay: Infinity });
236
+ try {
237
+ for await (const line of rl) {
238
+ await handler(line);
199
239
  }
200
- output.push(line);
240
+ } finally {
241
+ rl.close();
242
+ stream.close();
243
+ }
244
+ }
245
+
246
+ async function writeLine(stream, line) {
247
+ if (!stream.write(`${line}\n`)) {
248
+ await once(stream, "drain");
201
249
  }
202
- return output.join("\n");
203
250
  }
204
251
 
205
252
  async function hashFile(filePath) {