neosqlite 1.0.0

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.
@@ -0,0 +1,56 @@
1
+ import fs from "fs/promises";
2
+ import { CliCommandOptions } from "../../types";
3
+
4
+ import type { MigrationFile } from "..";
5
+
6
+ import { createClient } from "../../client";
7
+ import { createMigrationTableIfNotExists, logSuccess, logWarn, validateMigrationDirectory } from "../util";
8
+
9
+ export default async function Up({ migrationPath, migrationTable, ...config }: CliCommandOptions) {
10
+ const db = createClient(config);
11
+
12
+ await validateMigrationDirectory(migrationPath);
13
+ createMigrationTableIfNotExists(db, migrationTable);
14
+
15
+ // Query all of the migrations that have already been ran
16
+ const result = db.read("SELECT filepath FROM " + migrationTable);
17
+ const migratedFiles = result.map((row) => String(row["filepath"]));
18
+
19
+ const files = await fs.readdir(migrationPath);
20
+ const migrationFiles = files.filter((file) => {
21
+ const fileName = file.split(".")[0];
22
+ return (file.endsWith(".ts") || file.endsWith(".js")) && !migratedFiles.includes(fileName);
23
+ });
24
+
25
+ if (!migrationFiles.length) {
26
+ logWarn("No migrations to run");
27
+ process.exit(0);
28
+ }
29
+
30
+ // Sort the files by timestamp, in ascending order so that they run in the order they were created
31
+ migrationFiles.sort((a, b) => {
32
+ const aTimestamp = parseInt(a.split("_")[0]);
33
+ const bTimestamp = parseInt(b.split("_")[0]);
34
+ return aTimestamp - bTimestamp;
35
+ });
36
+
37
+ // Apply the migrations
38
+ for (const fileName of migrationFiles) {
39
+ const migration: MigrationFile = (await import(`${migrationPath}/${fileName}`)).default;
40
+ const normalizedFileName = fileName.split(".")[0];
41
+
42
+ // Run the migration
43
+ db.transaction(() => migration.up(db));
44
+
45
+ // Store the migration data
46
+ db.write({
47
+ sql: "INSERT INTO " + migrationTable + "(filepath, timestamp) VALUES (:filepath, :timestamp)",
48
+ args: { filepath: normalizedFileName, timestamp: Date.now() },
49
+ });
50
+
51
+ logSuccess(`Applied migration ${fileName}`);
52
+ }
53
+
54
+ logSuccess(`Successfully ran ${migrationFiles.length} migration(s)`);
55
+ process.exit(0);
56
+ }
@@ -0,0 +1,45 @@
1
+ import { Command } from "commander";
2
+
3
+ import Up from "./commands/up";
4
+ import New from "./commands/new";
5
+ import Down from "./commands/down";
6
+
7
+ import packageJSON from "../../package.json";
8
+ import { CliCommandOptions, NeosqliteClient } from "../types";
9
+
10
+ export interface MigrationFile {
11
+ up: (db: NeosqliteClient) => void;
12
+ down: (db: NeosqliteClient) => void;
13
+ }
14
+
15
+ export function createNeosqliteMigrationCli(props: CliCommandOptions) {
16
+ props.migrationTable = props.migrationTable ?? "migrations";
17
+
18
+ const program = new Command();
19
+
20
+ program.name("migrate").description("Migration CLI").version(packageJSON.version);
21
+
22
+ program
23
+ .command("up")
24
+ .description("Apply all unapplied migrations")
25
+ .action(() => Up(props));
26
+
27
+ program
28
+ .command("down")
29
+ .description("Undo the last migration")
30
+ .action(() => Down(props));
31
+
32
+ program
33
+ .command("new")
34
+ .option("-n, --name <name>", "The name of the migration")
35
+ .action((options) => {
36
+ if (!options.name) {
37
+ console.error("Please provide a name for the migration. Use the --name flag");
38
+ process.exit(1);
39
+ }
40
+
41
+ New({ ...props, name: options.name });
42
+ });
43
+
44
+ program.parse(process.argv);
45
+ }
@@ -0,0 +1,46 @@
1
+ import fs from "fs/promises";
2
+
3
+ import { NeosqliteClient } from "../types";
4
+ import { queryString } from "../client/util";
5
+
6
+ const RESET = "\x1b[0m";
7
+ const RED = "\x1b[31m";
8
+ const GREEN = "\x1b[32m";
9
+ const YELLOW = "\x1b[33m";
10
+
11
+ const CHECK = "✔";
12
+ const CROSS = "✖";
13
+ const WARN = "⚠";
14
+
15
+ export const logSuccess = (message: string) => {
16
+ console.log(`${GREEN}${CHECK}${RESET} ${message}`);
17
+ };
18
+
19
+ export const logError = (message: string) => {
20
+ console.log(`${RED}${CROSS}${RESET} ${message}`);
21
+ };
22
+
23
+ export const logWarn = (message: string) => {
24
+ console.log(`${YELLOW}${WARN}${RESET} ${message}`);
25
+ };
26
+
27
+ export const createMigrationTableIfNotExists = (db: NeosqliteClient, migrationTable: string = "migrations") => {
28
+ return db.write({
29
+ sql: queryString(
30
+ "CREATE TABLE IF NOT EXISTS " + migrationTable + "(",
31
+ " id INTEGER PRIMARY KEY AUTOINCREMENT,",
32
+ " filepath TEXT,",
33
+ " timestamp INTEGER",
34
+ ")",
35
+ ),
36
+ });
37
+ };
38
+
39
+ export const validateMigrationDirectory = async (migrationPath: string) => {
40
+ try {
41
+ await fs.access(migrationPath);
42
+ } catch (e) {
43
+ logError("Unable to find the migration directory. Make sure it exists");
44
+ process.exit(0);
45
+ }
46
+ };
package/src/types.ts ADDED
@@ -0,0 +1,98 @@
1
+ import type { BackupMetadata, BackupOptions, PragmaOptions } from "better-sqlite3";
2
+
3
+ export type NeosqliteConfig = {
4
+ /** The filename of the database */
5
+ file: ":memory:" | string;
6
+
7
+ /** When a query is logged using `logQuery: true`, you will receive the full query string via this callback */
8
+ onQueryLog?: (query: string) => void;
9
+
10
+ /** When a query finishes, gather data about the query from this callback */
11
+ onQueryComplete?: (data: OnQueryFinishData) => void;
12
+ };
13
+
14
+ export type NeosqliteClient = {
15
+ /** Call a pragma */
16
+ pragma: (source: string, options?: PragmaOptions | undefined) => unknown;
17
+
18
+ /** Backup the database */
19
+ backup: (destinationFile: string, options?: BackupOptions | undefined) => Promise<BackupMetadata>;
20
+
21
+ /** Create a transaction and execute queries within it */
22
+ transaction: (fn: Function) => void;
23
+
24
+ /** Read data from the database, returns all matching rows */
25
+ read: (params: ExecuteQueryParams) => Row[];
26
+
27
+ /** Read data from the database, returns the first matching row */
28
+ readOne: (params: ExecuteQueryParams) => Row | undefined;
29
+
30
+ /** Write data to the database, returns all matching rows via RETURNING */
31
+ writeReturning: (params: ExecuteQueryParams) => Row[];
32
+
33
+ /** Write data to the database, returns the number of rows affected */
34
+ write: (params: ExecuteQueryParams) => { changes: number; lastInsertedRowid: number };
35
+ };
36
+
37
+ export type OnQueryFinishData = {
38
+ /** The query that was executed */
39
+ query: string;
40
+
41
+ /** The time it took to execute the query in milliseconds */
42
+ time: number;
43
+ };
44
+
45
+ export type QueryParams = {
46
+ /** The SQL query to execute */
47
+ sql: string;
48
+
49
+ /** When true, the query string will be sent to onQueryLog */
50
+ logQuery?: boolean;
51
+
52
+ /** The values to bind to the query string */
53
+ args?: Record<string, number | string | boolean | null>;
54
+ };
55
+
56
+ export type Row = Record<string, number | string | boolean | null>;
57
+
58
+ export type ExecuteQueryParams = string | QueryParams;
59
+
60
+ export enum JobPriority {
61
+ Low = 1,
62
+ Medium = 2,
63
+ High = 3,
64
+ }
65
+
66
+ export enum JobStatus {
67
+ Pending = "pending",
68
+ Running = "running",
69
+ Failed = "failed",
70
+ Completed = "completed",
71
+ }
72
+
73
+ export interface CliCommandOptions extends NeosqliteConfig {
74
+ /** The path containing the migration files */
75
+ migrationPath: string;
76
+
77
+ /** The name of the table to store migrations in. Defaults to "migrations" */
78
+ migrationTable?: string;
79
+ }
80
+
81
+ export interface CreateJobsOptions extends NeosqliteConfig {
82
+ /** The name of the table to store jobs in. Defaults to "jobs" */
83
+ jobsTable?: string;
84
+
85
+ /** How often to process jobs in milliseconds. Defaults to 5000 */
86
+ processEvery?: number;
87
+
88
+ /** The maximum number of retries for each job. Defaults to 3 */
89
+ maxRetries?: number;
90
+
91
+ /** The maximum number of jobs to execute at once. Defaults to 10 */
92
+ maxJobs?: number;
93
+ }
94
+
95
+ export interface JobOptions {
96
+ /** The priority of the job */
97
+ priority?: number;
98
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,13 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2021",
4
+ "module": "commonjs",
5
+ "declaration": true,
6
+ "outDir": "./lib",
7
+ "strict": true,
8
+ "resolveJsonModule": true,
9
+ "esModuleInterop": true
10
+ },
11
+ "include": ["src"],
12
+ "exclude": ["node_modules"]
13
+ }