datasette-ts 0.0.15 → 0.0.17
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/dist/cli.js +203 -301
- package/dist/cli.js.map +3 -3
- package/package.json +1 -1
- package/scripts/cloudflare-deploy-helpers.mjs +199 -273
package/dist/cli.js
CHANGED
|
@@ -6372,7 +6372,7 @@ function openNodeSqlite(options) {
|
|
|
6372
6372
|
sql: sql2,
|
|
6373
6373
|
args: params ?? []
|
|
6374
6374
|
});
|
|
6375
|
-
const rows =
|
|
6375
|
+
const rows = normalizeLibsqlRows(result);
|
|
6376
6376
|
const columns = normalizeLibsqlColumns(result, rows);
|
|
6377
6377
|
return { rows, columns };
|
|
6378
6378
|
},
|
|
@@ -6409,7 +6409,7 @@ function normalizeLibsqlColumns(result, rows) {
|
|
|
6409
6409
|
}
|
|
6410
6410
|
return rows[0] ? Object.keys(rows[0]) : [];
|
|
6411
6411
|
}
|
|
6412
|
-
function
|
|
6412
|
+
function normalizeLibsqlRows(result) {
|
|
6413
6413
|
const rows = Array.isArray(result.rows) ? result.rows : [];
|
|
6414
6414
|
if (!rows.length) {
|
|
6415
6415
|
return [];
|
|
@@ -6471,7 +6471,7 @@ var init_sqlite = __esm({
|
|
|
6471
6471
|
});
|
|
6472
6472
|
|
|
6473
6473
|
// src/db/introspection.ts
|
|
6474
|
-
function
|
|
6474
|
+
function asString(value, fallback = "") {
|
|
6475
6475
|
if (typeof value === "string") {
|
|
6476
6476
|
return value;
|
|
6477
6477
|
}
|
|
@@ -6480,7 +6480,7 @@ function asString2(value, fallback = "") {
|
|
|
6480
6480
|
}
|
|
6481
6481
|
return String(value);
|
|
6482
6482
|
}
|
|
6483
|
-
function
|
|
6483
|
+
function asNumber(value, fallback = 0) {
|
|
6484
6484
|
if (typeof value === "number") {
|
|
6485
6485
|
return value;
|
|
6486
6486
|
}
|
|
@@ -6490,7 +6490,7 @@ function asNumber2(value, fallback = 0) {
|
|
|
6490
6490
|
}
|
|
6491
6491
|
return fallback;
|
|
6492
6492
|
}
|
|
6493
|
-
function
|
|
6493
|
+
function asNullableString(value) {
|
|
6494
6494
|
if (value == null) {
|
|
6495
6495
|
return null;
|
|
6496
6496
|
}
|
|
@@ -6516,7 +6516,7 @@ async function listTables(client) {
|
|
|
6516
6516
|
"select name, type, sql from sqlite_master where type in ('table', 'view') order by name"
|
|
6517
6517
|
);
|
|
6518
6518
|
return rows.filter((row) => {
|
|
6519
|
-
const name =
|
|
6519
|
+
const name = asString(row.name);
|
|
6520
6520
|
return !name.startsWith(SQLITE_INTERNAL_PREFIX);
|
|
6521
6521
|
});
|
|
6522
6522
|
}
|
|
@@ -6525,12 +6525,12 @@ async function tableColumns2(client, tableName) {
|
|
|
6525
6525
|
`pragma table_info(${escapeIdentifier2(tableName)})`
|
|
6526
6526
|
);
|
|
6527
6527
|
return rows.map((row) => ({
|
|
6528
|
-
name:
|
|
6529
|
-
type:
|
|
6530
|
-
notNull:
|
|
6531
|
-
defaultValue:
|
|
6532
|
-
primaryKey:
|
|
6533
|
-
primaryKeyIndex:
|
|
6528
|
+
name: asString(row.name),
|
|
6529
|
+
type: asString(row.type),
|
|
6530
|
+
notNull: asNumber(row.notnull) === 1,
|
|
6531
|
+
defaultValue: asNullableString(row.dflt_value),
|
|
6532
|
+
primaryKey: asNumber(row.pk) > 0,
|
|
6533
|
+
primaryKeyIndex: asNumber(row.pk)
|
|
6534
6534
|
}));
|
|
6535
6535
|
}
|
|
6536
6536
|
async function tableForeignKeys2(client, tableName) {
|
|
@@ -6538,23 +6538,23 @@ async function tableForeignKeys2(client, tableName) {
|
|
|
6538
6538
|
`pragma foreign_key_list(${escapeIdentifier2(tableName)})`
|
|
6539
6539
|
);
|
|
6540
6540
|
return rows.map((row) => ({
|
|
6541
|
-
table:
|
|
6542
|
-
from:
|
|
6543
|
-
to:
|
|
6544
|
-
onUpdate:
|
|
6545
|
-
onDelete:
|
|
6546
|
-
match:
|
|
6547
|
-
seq:
|
|
6548
|
-
id:
|
|
6541
|
+
table: asString(row.table),
|
|
6542
|
+
from: asString(row.from),
|
|
6543
|
+
to: asString(row.to),
|
|
6544
|
+
onUpdate: asString(row.on_update),
|
|
6545
|
+
onDelete: asString(row.on_delete),
|
|
6546
|
+
match: asString(row.match),
|
|
6547
|
+
seq: asNumber(row.seq),
|
|
6548
|
+
id: asNumber(row.id)
|
|
6549
6549
|
}));
|
|
6550
6550
|
}
|
|
6551
6551
|
async function introspectDatabase2(client) {
|
|
6552
6552
|
const rows = await listTables(client);
|
|
6553
6553
|
const tables = {};
|
|
6554
6554
|
for (const row of rows) {
|
|
6555
|
-
const name =
|
|
6556
|
-
const type =
|
|
6557
|
-
const sql2 =
|
|
6555
|
+
const name = asString(row.name);
|
|
6556
|
+
const type = asString(row.type);
|
|
6557
|
+
const sql2 = asNullableString(row.sql);
|
|
6558
6558
|
const virtual = type === "table" && isVirtualTable2(sql2);
|
|
6559
6559
|
const infoType = type === "view" ? "view" : virtual ? "virtual" : "table";
|
|
6560
6560
|
const columns = await tableColumns2(client, name);
|
|
@@ -23689,38 +23689,117 @@ var init_node4 = __esm({
|
|
|
23689
23689
|
import { parseArgs as parseArgs2 } from "node:util";
|
|
23690
23690
|
|
|
23691
23691
|
// src/cli/deploy-cloudflare.ts
|
|
23692
|
-
import { mkdir as mkdir2, stat as stat2, writeFile } from "node:fs/promises";
|
|
23692
|
+
import { mkdir as mkdir2, stat as stat2, writeFile as writeFile2 } from "node:fs/promises";
|
|
23693
23693
|
import { dirname, extname, join, relative, resolve } from "node:path";
|
|
23694
23694
|
import { fileURLToPath } from "node:url";
|
|
23695
23695
|
import alchemy from "alchemy";
|
|
23696
23696
|
import { Assets, D1Database, Worker } from "alchemy/cloudflare";
|
|
23697
23697
|
|
|
23698
23698
|
// scripts/cloudflare-deploy-helpers.mjs
|
|
23699
|
-
import { spawn } from "node:child_process";
|
|
23700
23699
|
import { createHash } from "node:crypto";
|
|
23701
|
-
import { createReadStream
|
|
23702
|
-
import { mkdir, stat,
|
|
23703
|
-
import { once } from "node:events";
|
|
23700
|
+
import { createReadStream } from "node:fs";
|
|
23701
|
+
import { mkdir, stat, writeFile } from "node:fs/promises";
|
|
23704
23702
|
import path from "node:path";
|
|
23705
|
-
import { createInterface } from "node:readline";
|
|
23706
|
-
import { pipeline } from "node:stream/promises";
|
|
23707
23703
|
import { pathToFileURL } from "node:url";
|
|
23708
23704
|
import { createClient } from "@libsql/client";
|
|
23709
23705
|
async function dumpSqliteForD1(options) {
|
|
23710
23706
|
const baseName = options.outputName ?? path.basename(options.dbFile, path.extname(options.dbFile));
|
|
23711
23707
|
const outputPath = path.join(options.outputDir, `${baseName}.sql`);
|
|
23712
|
-
const
|
|
23713
|
-
const log = typeof options.log === "function" ? options.log : null;
|
|
23714
|
-
const progressIntervalMs = typeof options.progressIntervalMs === "number" && Number.isFinite(options.progressIntervalMs) ? options.progressIntervalMs : 5e3;
|
|
23708
|
+
const log = options.log ?? null;
|
|
23715
23709
|
await mkdir(options.outputDir, { recursive: true });
|
|
23716
|
-
|
|
23710
|
+
log?.("Generating D1 import SQL");
|
|
23711
|
+
const db = createReadonlyClient(options.dbFile);
|
|
23717
23712
|
try {
|
|
23718
|
-
await
|
|
23713
|
+
const sql2 = await generateD1Dump(db, log);
|
|
23714
|
+
await writeFile(outputPath, sql2, "utf8");
|
|
23715
|
+
const { size } = await stat(outputPath);
|
|
23716
|
+
log?.(`D1 import SQL ready (${formatBytes(size)})`);
|
|
23719
23717
|
} finally {
|
|
23720
|
-
|
|
23718
|
+
db.close();
|
|
23721
23719
|
}
|
|
23722
23720
|
return outputPath;
|
|
23723
23721
|
}
|
|
23722
|
+
async function generateD1Dump(db, log) {
|
|
23723
|
+
const chunks = [];
|
|
23724
|
+
const objects = await executeRows(
|
|
23725
|
+
db,
|
|
23726
|
+
`SELECT type, name, sql, tbl_name
|
|
23727
|
+
FROM sqlite_master
|
|
23728
|
+
WHERE sql IS NOT NULL
|
|
23729
|
+
AND name NOT LIKE 'sqlite_%'
|
|
23730
|
+
ORDER BY
|
|
23731
|
+
CASE type
|
|
23732
|
+
WHEN 'table' THEN 1
|
|
23733
|
+
WHEN 'index' THEN 2
|
|
23734
|
+
WHEN 'trigger' THEN 3
|
|
23735
|
+
WHEN 'view' THEN 4
|
|
23736
|
+
ELSE 5
|
|
23737
|
+
END,
|
|
23738
|
+
name`
|
|
23739
|
+
);
|
|
23740
|
+
const tables = objects.filter((o) => o.type === "table" && !isVirtualTable(o.sql));
|
|
23741
|
+
const views = objects.filter((o) => o.type === "view");
|
|
23742
|
+
const indexes = objects.filter((o) => o.type === "index");
|
|
23743
|
+
const triggers = objects.filter((o) => o.type === "trigger");
|
|
23744
|
+
for (const view of [...views].reverse()) {
|
|
23745
|
+
chunks.push(`DROP VIEW IF EXISTS ${escapeIdentifier(view.name)};`);
|
|
23746
|
+
}
|
|
23747
|
+
for (const table of [...tables].reverse()) {
|
|
23748
|
+
chunks.push(`DROP TABLE IF EXISTS ${escapeIdentifier(table.name)};`);
|
|
23749
|
+
}
|
|
23750
|
+
for (const table of tables) {
|
|
23751
|
+
chunks.push(`${normalizeSql(table.sql)};`);
|
|
23752
|
+
}
|
|
23753
|
+
let totalRows = 0;
|
|
23754
|
+
for (const table of tables) {
|
|
23755
|
+
const rowCount = await dumpTableData(db, table.name, chunks);
|
|
23756
|
+
totalRows += rowCount;
|
|
23757
|
+
}
|
|
23758
|
+
log?.(`Exported ${totalRows.toLocaleString()} rows from ${tables.length} tables`);
|
|
23759
|
+
for (const index of indexes) {
|
|
23760
|
+
if (!index.sql) continue;
|
|
23761
|
+
chunks.push(`${normalizeSql(index.sql)};`);
|
|
23762
|
+
}
|
|
23763
|
+
for (const view of views) {
|
|
23764
|
+
chunks.push(`${normalizeSql(view.sql)};`);
|
|
23765
|
+
}
|
|
23766
|
+
for (const trigger of triggers) {
|
|
23767
|
+
chunks.push(`${normalizeSql(trigger.sql)};`);
|
|
23768
|
+
}
|
|
23769
|
+
return chunks.join("\n") + "\n";
|
|
23770
|
+
}
|
|
23771
|
+
async function dumpTableData(db, tableName, chunks) {
|
|
23772
|
+
const rows = await executeRows(db, `SELECT * FROM ${escapeIdentifier(tableName)}`);
|
|
23773
|
+
if (rows.length === 0) return 0;
|
|
23774
|
+
const columns = Object.keys(rows[0]);
|
|
23775
|
+
const columnList = columns.map(escapeIdentifier).join(", ");
|
|
23776
|
+
for (const row of rows) {
|
|
23777
|
+
const values = columns.map((col) => escapeSqlValue(row[col])).join(", ");
|
|
23778
|
+
chunks.push(`INSERT INTO ${escapeIdentifier(tableName)} (${columnList}) VALUES (${values});`);
|
|
23779
|
+
}
|
|
23780
|
+
return rows.length;
|
|
23781
|
+
}
|
|
23782
|
+
function escapeSqlValue(value) {
|
|
23783
|
+
if (value === null || value === void 0) {
|
|
23784
|
+
return "NULL";
|
|
23785
|
+
}
|
|
23786
|
+
if (typeof value === "bigint") {
|
|
23787
|
+
return String(value);
|
|
23788
|
+
}
|
|
23789
|
+
if (typeof value === "number") {
|
|
23790
|
+
return Number.isFinite(value) ? String(value) : "NULL";
|
|
23791
|
+
}
|
|
23792
|
+
if (typeof value === "boolean") {
|
|
23793
|
+
return value ? "1" : "0";
|
|
23794
|
+
}
|
|
23795
|
+
if (value instanceof Uint8Array || Buffer.isBuffer(value)) {
|
|
23796
|
+
return `X'${Buffer.from(value).toString("hex")}'`;
|
|
23797
|
+
}
|
|
23798
|
+
return `'${String(value).replace(/'/g, "''")}'`;
|
|
23799
|
+
}
|
|
23800
|
+
function normalizeSql(sql2) {
|
|
23801
|
+
return sql2.replace(/\s+/g, " ").trim();
|
|
23802
|
+
}
|
|
23724
23803
|
async function loadSchemaFromFile(dbFile) {
|
|
23725
23804
|
const db = createReadonlyClient(dbFile);
|
|
23726
23805
|
try {
|
|
@@ -23730,9 +23809,11 @@ async function loadSchemaFromFile(dbFile) {
|
|
|
23730
23809
|
}
|
|
23731
23810
|
}
|
|
23732
23811
|
async function loadInspectDataFromFile(dbFile, databaseName) {
|
|
23733
|
-
const stats = await
|
|
23734
|
-
|
|
23735
|
-
|
|
23812
|
+
const [stats, hash, tables] = await Promise.all([
|
|
23813
|
+
stat(dbFile),
|
|
23814
|
+
hashFile(dbFile),
|
|
23815
|
+
countTables(dbFile)
|
|
23816
|
+
]);
|
|
23736
23817
|
return {
|
|
23737
23818
|
[databaseName]: {
|
|
23738
23819
|
hash,
|
|
@@ -23745,22 +23826,21 @@ async function loadInspectDataFromFile(dbFile, databaseName) {
|
|
|
23745
23826
|
async function introspectDatabase(db) {
|
|
23746
23827
|
const rows = await executeRows(
|
|
23747
23828
|
db,
|
|
23748
|
-
"
|
|
23829
|
+
"SELECT name, type, sql FROM sqlite_master WHERE type IN ('table', 'view') ORDER BY name"
|
|
23749
23830
|
);
|
|
23750
23831
|
const tables = {};
|
|
23751
23832
|
for (const row of rows) {
|
|
23752
|
-
const name =
|
|
23753
|
-
if (name.startsWith("sqlite_"))
|
|
23754
|
-
|
|
23755
|
-
|
|
23756
|
-
const type = asString(row.type);
|
|
23757
|
-
const sql2 = asNullableString(row.sql);
|
|
23833
|
+
const name = String(row.name ?? "");
|
|
23834
|
+
if (name.startsWith("sqlite_")) continue;
|
|
23835
|
+
const type = String(row.type ?? "");
|
|
23836
|
+
const sql2 = row.sql ? String(row.sql) : null;
|
|
23758
23837
|
const virtual = type === "table" && isVirtualTable(sql2);
|
|
23759
23838
|
const infoType = type === "view" ? "view" : virtual ? "virtual" : "table";
|
|
23760
|
-
const columns = await
|
|
23761
|
-
|
|
23762
|
-
|
|
23763
|
-
|
|
23839
|
+
const [columns, foreignKeys] = await Promise.all([
|
|
23840
|
+
tableColumns(db, name),
|
|
23841
|
+
tableForeignKeys(db, name)
|
|
23842
|
+
]);
|
|
23843
|
+
const primaryKeys = columns.filter((col) => col.primaryKey).sort((a, b) => a.primaryKeyIndex - b.primaryKeyIndex).map((col) => col.name);
|
|
23764
23844
|
tables[name] = {
|
|
23765
23845
|
name,
|
|
23766
23846
|
type: infoType,
|
|
@@ -23768,273 +23848,112 @@ async function introspectDatabase(db) {
|
|
|
23768
23848
|
columns,
|
|
23769
23849
|
primaryKeys,
|
|
23770
23850
|
foreignKeys,
|
|
23771
|
-
isFts
|
|
23851
|
+
isFts: isFtsTable(sql2)
|
|
23772
23852
|
};
|
|
23773
23853
|
}
|
|
23774
23854
|
return { tables };
|
|
23775
23855
|
}
|
|
23776
23856
|
async function tableColumns(db, tableName) {
|
|
23777
|
-
const rows = await executeRows(db, `
|
|
23857
|
+
const rows = await executeRows(db, `PRAGMA table_info(${escapeIdentifier(tableName)})`);
|
|
23778
23858
|
return rows.map((row) => ({
|
|
23779
|
-
name:
|
|
23780
|
-
type:
|
|
23781
|
-
notNull:
|
|
23782
|
-
defaultValue:
|
|
23783
|
-
primaryKey:
|
|
23784
|
-
primaryKeyIndex:
|
|
23859
|
+
name: String(row.name ?? ""),
|
|
23860
|
+
type: String(row.type ?? ""),
|
|
23861
|
+
notNull: Number(row.notnull) === 1,
|
|
23862
|
+
defaultValue: row.dflt_value != null ? String(row.dflt_value) : null,
|
|
23863
|
+
primaryKey: Number(row.pk) > 0,
|
|
23864
|
+
primaryKeyIndex: Number(row.pk)
|
|
23785
23865
|
}));
|
|
23786
23866
|
}
|
|
23787
23867
|
async function tableForeignKeys(db, tableName) {
|
|
23788
|
-
const rows = await executeRows(db, `
|
|
23868
|
+
const rows = await executeRows(db, `PRAGMA foreign_key_list(${escapeIdentifier(tableName)})`);
|
|
23789
23869
|
return rows.map((row) => ({
|
|
23790
|
-
table:
|
|
23791
|
-
from:
|
|
23792
|
-
to:
|
|
23793
|
-
onUpdate:
|
|
23794
|
-
onDelete:
|
|
23795
|
-
match:
|
|
23796
|
-
seq:
|
|
23797
|
-
id:
|
|
23870
|
+
table: String(row.table ?? ""),
|
|
23871
|
+
from: String(row.from ?? ""),
|
|
23872
|
+
to: String(row.to ?? ""),
|
|
23873
|
+
onUpdate: String(row.on_update ?? ""),
|
|
23874
|
+
onDelete: String(row.on_delete ?? ""),
|
|
23875
|
+
match: String(row.match ?? ""),
|
|
23876
|
+
seq: Number(row.seq ?? 0),
|
|
23877
|
+
id: Number(row.id ?? 0)
|
|
23798
23878
|
}));
|
|
23799
23879
|
}
|
|
23800
|
-
function asString(value, fallback = "") {
|
|
23801
|
-
if (typeof value === "string") {
|
|
23802
|
-
return value;
|
|
23803
|
-
}
|
|
23804
|
-
if (value == null) {
|
|
23805
|
-
return fallback;
|
|
23806
|
-
}
|
|
23807
|
-
return String(value);
|
|
23808
|
-
}
|
|
23809
|
-
function asNumber(value, fallback = 0) {
|
|
23810
|
-
if (typeof value === "number") {
|
|
23811
|
-
return value;
|
|
23812
|
-
}
|
|
23813
|
-
if (typeof value === "string" && value.trim() !== "") {
|
|
23814
|
-
const parsed = Number(value);
|
|
23815
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
23816
|
-
}
|
|
23817
|
-
return fallback;
|
|
23818
|
-
}
|
|
23819
|
-
function asNullableString(value) {
|
|
23820
|
-
if (value == null) {
|
|
23821
|
-
return null;
|
|
23822
|
-
}
|
|
23823
|
-
if (typeof value === "string") {
|
|
23824
|
-
return value;
|
|
23825
|
-
}
|
|
23826
|
-
return String(value);
|
|
23827
|
-
}
|
|
23828
23880
|
function isVirtualTable(sql2) {
|
|
23829
|
-
|
|
23830
|
-
return false;
|
|
23831
|
-
}
|
|
23832
|
-
return /create\s+virtual\s+table/i.test(sql2);
|
|
23881
|
+
return sql2 ? /create\s+virtual\s+table/i.test(sql2) : false;
|
|
23833
23882
|
}
|
|
23834
23883
|
function isFtsTable(sql2) {
|
|
23835
|
-
|
|
23836
|
-
return false;
|
|
23837
|
-
}
|
|
23838
|
-
return /using\s+fts/i.test(sql2);
|
|
23884
|
+
return sql2 ? /using\s+fts/i.test(sql2) : false;
|
|
23839
23885
|
}
|
|
23840
23886
|
function escapeIdentifier(name) {
|
|
23841
|
-
|
|
23842
|
-
return `"${escaped}"`;
|
|
23843
|
-
}
|
|
23844
|
-
async function normalizeDumpFile(inputPath, outputPath, { log } = {}) {
|
|
23845
|
-
const tablesInOrder = [];
|
|
23846
|
-
const viewsInOrder = [];
|
|
23847
|
-
if (log) {
|
|
23848
|
-
log(`Normalizing D1 import SQL: ${outputPath}`);
|
|
23849
|
-
}
|
|
23850
|
-
await forEachLine(inputPath, (line) => {
|
|
23851
|
-
const tableMatch = line.match(/^CREATE TABLE\s+("?[^"]+"?)/i);
|
|
23852
|
-
if (tableMatch) {
|
|
23853
|
-
tablesInOrder.push(tableMatch[1].replace(/\s*\($/, ""));
|
|
23854
|
-
return;
|
|
23855
|
-
}
|
|
23856
|
-
const viewMatch = line.match(/^CREATE VIEW\s+("?[^"]+"?)/i);
|
|
23857
|
-
if (viewMatch) {
|
|
23858
|
-
viewsInOrder.push(viewMatch[1].replace(/\s*\($/, ""));
|
|
23859
|
-
}
|
|
23860
|
-
});
|
|
23861
|
-
const outputStream = createWriteStream(outputPath, { encoding: "utf8" });
|
|
23862
|
-
for (const viewName of viewsInOrder.reverse()) {
|
|
23863
|
-
await writeLine(outputStream, `DROP VIEW IF EXISTS ${viewName};`);
|
|
23864
|
-
}
|
|
23865
|
-
for (const tableName of tablesInOrder.reverse()) {
|
|
23866
|
-
await writeLine(outputStream, `DROP TABLE IF EXISTS ${tableName};`);
|
|
23867
|
-
}
|
|
23868
|
-
await forEachLine(inputPath, async (line) => {
|
|
23869
|
-
if (line === "BEGIN TRANSACTION;" || line === "COMMIT;") {
|
|
23870
|
-
return;
|
|
23871
|
-
}
|
|
23872
|
-
if (line.startsWith("PRAGMA foreign_keys=")) {
|
|
23873
|
-
return;
|
|
23874
|
-
}
|
|
23875
|
-
await writeLine(outputStream, line);
|
|
23876
|
-
});
|
|
23877
|
-
outputStream.end();
|
|
23878
|
-
await once(outputStream, "finish");
|
|
23879
|
-
if (log) {
|
|
23880
|
-
const { size } = await stat(outputPath);
|
|
23881
|
-
log(`D1 import SQL ready (${formatBytes(size)})`);
|
|
23882
|
-
}
|
|
23883
|
-
}
|
|
23884
|
-
async function dumpSqliteToFile(dbFile, outputPath, { log, progressIntervalMs } = {}) {
|
|
23885
|
-
if (log) {
|
|
23886
|
-
log(`Dumping SQLite database via sqlite3 .dump`);
|
|
23887
|
-
}
|
|
23888
|
-
const child = spawn("sqlite3", [dbFile, ".dump"], {
|
|
23889
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
23890
|
-
});
|
|
23891
|
-
let stderr = "";
|
|
23892
|
-
if (child.stderr) {
|
|
23893
|
-
child.stderr.setEncoding("utf8");
|
|
23894
|
-
child.stderr.on("data", (chunk) => {
|
|
23895
|
-
stderr += chunk;
|
|
23896
|
-
});
|
|
23897
|
-
}
|
|
23898
|
-
const stdout = child.stdout;
|
|
23899
|
-
if (!stdout) {
|
|
23900
|
-
const [error] = await once(child, "error");
|
|
23901
|
-
throw error ?? new Error("sqlite3 stdout is unavailable.");
|
|
23902
|
-
}
|
|
23903
|
-
const outputStream = createWriteStream(outputPath, { encoding: "utf8" });
|
|
23904
|
-
child.once("error", (error) => {
|
|
23905
|
-
stdout.destroy(error);
|
|
23906
|
-
outputStream.destroy(error);
|
|
23907
|
-
});
|
|
23908
|
-
const stopProgress = log ? startProgressLogger(outputPath, log, progressIntervalMs) : () => void 0;
|
|
23909
|
-
try {
|
|
23910
|
-
await pipeline(stdout, outputStream);
|
|
23911
|
-
} finally {
|
|
23912
|
-
stopProgress();
|
|
23913
|
-
}
|
|
23914
|
-
const [code, signal] = await once(child, "close");
|
|
23915
|
-
if (code !== 0) {
|
|
23916
|
-
const suffix = signal ? ` (signal ${signal})` : "";
|
|
23917
|
-
const message = stderr.trim() || `sqlite3 exited with code ${code ?? "unknown"}${suffix}`;
|
|
23918
|
-
throw new Error(message);
|
|
23919
|
-
}
|
|
23920
|
-
if (log) {
|
|
23921
|
-
const { size } = await stat(outputPath);
|
|
23922
|
-
log(`SQLite dump completed (${formatBytes(size)})`);
|
|
23923
|
-
}
|
|
23924
|
-
}
|
|
23925
|
-
async function forEachLine(filePath, handler) {
|
|
23926
|
-
const stream = createReadStream(filePath, { encoding: "utf8" });
|
|
23927
|
-
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
23928
|
-
try {
|
|
23929
|
-
for await (const line of rl) {
|
|
23930
|
-
await handler(line);
|
|
23931
|
-
}
|
|
23932
|
-
} finally {
|
|
23933
|
-
rl.close();
|
|
23934
|
-
stream.close();
|
|
23935
|
-
}
|
|
23936
|
-
}
|
|
23937
|
-
async function writeLine(stream, line) {
|
|
23938
|
-
if (!stream.write(`${line}
|
|
23939
|
-
`)) {
|
|
23940
|
-
await once(stream, "drain");
|
|
23941
|
-
}
|
|
23942
|
-
}
|
|
23943
|
-
function startProgressLogger(filePath, log, intervalMs) {
|
|
23944
|
-
const interval = setInterval(() => {
|
|
23945
|
-
void stat(filePath).then((info) => {
|
|
23946
|
-
log(`Dump size: ${formatBytes(info.size)}`);
|
|
23947
|
-
}).catch(() => void 0);
|
|
23948
|
-
}, intervalMs);
|
|
23949
|
-
return () => clearInterval(interval);
|
|
23887
|
+
return `"${String(name).replace(/"/g, '""')}"`;
|
|
23950
23888
|
}
|
|
23951
23889
|
function formatBytes(bytes) {
|
|
23952
|
-
if (!Number.isFinite(bytes) || bytes < 0)
|
|
23953
|
-
return "0 B";
|
|
23954
|
-
}
|
|
23890
|
+
if (!Number.isFinite(bytes) || bytes < 0) return "0 B";
|
|
23955
23891
|
const units = ["B", "KB", "MB", "GB", "TB"];
|
|
23956
23892
|
let value = bytes;
|
|
23957
|
-
let
|
|
23958
|
-
while (value >= 1024 &&
|
|
23893
|
+
let i = 0;
|
|
23894
|
+
while (value >= 1024 && i < units.length - 1) {
|
|
23959
23895
|
value /= 1024;
|
|
23960
|
-
|
|
23896
|
+
i++;
|
|
23961
23897
|
}
|
|
23962
|
-
|
|
23963
|
-
return `${rounded} ${units[index]}`;
|
|
23898
|
+
return `${i === 0 ? value.toFixed(0) : value.toFixed(1)} ${units[i]}`;
|
|
23964
23899
|
}
|
|
23965
23900
|
async function hashFile(filePath) {
|
|
23966
|
-
|
|
23967
|
-
|
|
23968
|
-
|
|
23969
|
-
|
|
23970
|
-
|
|
23971
|
-
|
|
23972
|
-
});
|
|
23901
|
+
const hash = createHash("sha256");
|
|
23902
|
+
const stream = createReadStream(filePath, { highWaterMark: 1024 * 1024 });
|
|
23903
|
+
for await (const chunk of stream) {
|
|
23904
|
+
hash.update(chunk);
|
|
23905
|
+
}
|
|
23906
|
+
return hash.digest("hex");
|
|
23973
23907
|
}
|
|
23974
23908
|
async function countTables(dbFile) {
|
|
23975
23909
|
const db = createReadonlyClient(dbFile);
|
|
23976
23910
|
try {
|
|
23977
|
-
const rows = await executeRows(db, "
|
|
23911
|
+
const rows = await executeRows(db, "SELECT name FROM sqlite_master WHERE type = 'table'");
|
|
23978
23912
|
const tables = {};
|
|
23979
|
-
|
|
23980
|
-
|
|
23981
|
-
|
|
23982
|
-
|
|
23983
|
-
|
|
23984
|
-
|
|
23985
|
-
|
|
23986
|
-
|
|
23987
|
-
|
|
23988
|
-
|
|
23989
|
-
|
|
23990
|
-
|
|
23991
|
-
|
|
23992
|
-
|
|
23993
|
-
}
|
|
23913
|
+
await Promise.all(
|
|
23914
|
+
rows.map(async (row) => {
|
|
23915
|
+
const tableName = String(row.name ?? "");
|
|
23916
|
+
try {
|
|
23917
|
+
const [countRow] = await executeRows(
|
|
23918
|
+
db,
|
|
23919
|
+
`SELECT count(*) as count FROM ${escapeIdentifier(tableName)}`
|
|
23920
|
+
);
|
|
23921
|
+
tables[tableName] = { count: Number(countRow?.count ?? 0) };
|
|
23922
|
+
} catch {
|
|
23923
|
+
tables[tableName] = { count: 0 };
|
|
23924
|
+
}
|
|
23925
|
+
})
|
|
23926
|
+
);
|
|
23994
23927
|
return tables;
|
|
23995
23928
|
} finally {
|
|
23996
23929
|
db.close();
|
|
23997
23930
|
}
|
|
23998
23931
|
}
|
|
23999
23932
|
function createReadonlyClient(dbFile) {
|
|
24000
|
-
|
|
24001
|
-
|
|
23933
|
+
return createClient({
|
|
23934
|
+
url: pathToFileURL(dbFile).toString(),
|
|
23935
|
+
intMode: "bigint"
|
|
23936
|
+
// Handle integers > MAX_SAFE_INTEGER
|
|
23937
|
+
});
|
|
24002
23938
|
}
|
|
24003
23939
|
async function executeRows(db, sql2, args = []) {
|
|
24004
23940
|
const result = await db.execute({ sql: sql2, args });
|
|
24005
|
-
|
|
24006
|
-
|
|
24007
|
-
|
|
24008
|
-
|
|
24009
|
-
if (!rows.length) {
|
|
24010
|
-
return [];
|
|
24011
|
-
}
|
|
24012
|
-
const firstRow = rows[0];
|
|
24013
|
-
if (firstRow && typeof firstRow === "object" && !Array.isArray(firstRow)) {
|
|
23941
|
+
const rows = result?.rows ?? [];
|
|
23942
|
+
if (!rows.length) return [];
|
|
23943
|
+
const first = rows[0];
|
|
23944
|
+
if (first && typeof first === "object" && !Array.isArray(first)) {
|
|
24014
23945
|
return rows;
|
|
24015
23946
|
}
|
|
24016
|
-
const columns =
|
|
24017
|
-
|
|
24018
|
-
return column;
|
|
24019
|
-
}
|
|
24020
|
-
if (column && typeof column === "object" && "name" in column) {
|
|
24021
|
-
return String(column.name ?? "");
|
|
24022
|
-
}
|
|
24023
|
-
return "";
|
|
24024
|
-
}) : [];
|
|
24025
|
-
if (!columns.length) {
|
|
24026
|
-
return [];
|
|
24027
|
-
}
|
|
23947
|
+
const columns = result?.columns ?? [];
|
|
23948
|
+
if (!columns.length) return [];
|
|
24028
23949
|
return rows.map((row) => {
|
|
24029
23950
|
const values = Array.isArray(row) ? row : [];
|
|
24030
|
-
const
|
|
24031
|
-
for (let
|
|
24032
|
-
const
|
|
24033
|
-
if (
|
|
24034
|
-
mapped[column] = values[index];
|
|
24035
|
-
}
|
|
23951
|
+
const obj = {};
|
|
23952
|
+
for (let i = 0; i < columns.length; i++) {
|
|
23953
|
+
const col = typeof columns[i] === "string" ? columns[i] : columns[i]?.name ?? "";
|
|
23954
|
+
if (col) obj[col] = values[i];
|
|
24036
23955
|
}
|
|
24037
|
-
return
|
|
23956
|
+
return obj;
|
|
24038
23957
|
});
|
|
24039
23958
|
}
|
|
24040
23959
|
|
|
@@ -24056,25 +23975,14 @@ async function runCloudflareDeploy(args) {
|
|
|
24056
23975
|
logStep(`Worker entrypoint: ${workerEntrypoint}`);
|
|
24057
23976
|
const assetsPath = join(packageRoot, "public");
|
|
24058
23977
|
await assertFileExists(assetsPath, "assets directory");
|
|
24059
|
-
|
|
24060
|
-
|
|
24061
|
-
|
|
24062
|
-
|
|
24063
|
-
|
|
24064
|
-
|
|
24065
|
-
|
|
24066
|
-
|
|
24067
|
-
progressIntervalMs: 5e3
|
|
24068
|
-
});
|
|
24069
|
-
logStep(`D1 import file: ${importFile}`);
|
|
24070
|
-
} catch (error) {
|
|
24071
|
-
if (isSqliteCliMissing(error)) {
|
|
24072
|
-
throw new Error(
|
|
24073
|
-
"sqlite3 is required to export your database. Install sqlite3 and retry."
|
|
24074
|
-
);
|
|
24075
|
-
}
|
|
24076
|
-
throw error;
|
|
24077
|
-
}
|
|
23978
|
+
logStep("Exporting SQLite for D1 import");
|
|
23979
|
+
const importFile = await dumpSqliteForD1({
|
|
23980
|
+
dbFile: options.dbFile,
|
|
23981
|
+
outputDir: options.importsDir,
|
|
23982
|
+
outputName: options.d1Name,
|
|
23983
|
+
log: logStep
|
|
23984
|
+
});
|
|
23985
|
+
logStep(`D1 import file: ${importFile}`);
|
|
24078
23986
|
logStep("Loading schema");
|
|
24079
23987
|
const schema = await loadSchemaFromFile(options.dbFile);
|
|
24080
23988
|
let inspectData = null;
|
|
@@ -24326,12 +24234,6 @@ async function assertFileExists(path7, label) {
|
|
|
24326
24234
|
throw new Error(`${label} not found: ${path7}`);
|
|
24327
24235
|
}
|
|
24328
24236
|
}
|
|
24329
|
-
function isSqliteCliMissing(error) {
|
|
24330
|
-
if (!error || typeof error !== "object") {
|
|
24331
|
-
return false;
|
|
24332
|
-
}
|
|
24333
|
-
return "code" in error && error.code === "ENOENT";
|
|
24334
|
-
}
|
|
24335
24237
|
function logStep(message) {
|
|
24336
24238
|
console.log(`[datasette-ts] ${message}`);
|
|
24337
24239
|
}
|
|
@@ -24390,7 +24292,7 @@ async function createEmbeddedWorkerEntrypoint(options) {
|
|
|
24390
24292
|
"};",
|
|
24391
24293
|
""
|
|
24392
24294
|
].join("\n");
|
|
24393
|
-
await
|
|
24295
|
+
await writeFile2(entrypointPath, contents, "utf8");
|
|
24394
24296
|
options.log(`Embedded worker entrypoint: ${entrypointPath}`);
|
|
24395
24297
|
return entrypointPath;
|
|
24396
24298
|
}
|
|
@@ -24400,7 +24302,7 @@ function toPosixPath(value) {
|
|
|
24400
24302
|
|
|
24401
24303
|
// src/cli/serve.ts
|
|
24402
24304
|
init_registry();
|
|
24403
|
-
import { stat as stat5, writeFile as
|
|
24305
|
+
import { stat as stat5, writeFile as writeFile3 } from "node:fs/promises";
|
|
24404
24306
|
import { join as join2 } from "node:path";
|
|
24405
24307
|
|
|
24406
24308
|
// src/core/inspect.ts
|
|
@@ -24573,7 +24475,7 @@ async function runInspectCommand(args) {
|
|
|
24573
24475
|
const data = await inspectDatabases(databasePaths);
|
|
24574
24476
|
const json = JSON.stringify(data, null, 2);
|
|
24575
24477
|
if (inspectFile) {
|
|
24576
|
-
await
|
|
24478
|
+
await writeFile3(inspectFile, json, "utf8");
|
|
24577
24479
|
return;
|
|
24578
24480
|
}
|
|
24579
24481
|
process.stdout.write(`${json}
|