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.
package/.prettierrc ADDED
@@ -0,0 +1,12 @@
1
+ {
2
+ "tabWidth": 2,
3
+ "printWidth": 128,
4
+ "overrides": [
5
+ {
6
+ "files": ["*.ts", "*.js", "*.test.ts", "*.test.js"],
7
+ "options": {
8
+ "singleQuote": false
9
+ }
10
+ }
11
+ ]
12
+ }
package/database.db ADDED
Binary file
package/index.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { createClient } from "./src/client";
2
+
3
+ const db = createClient({
4
+ file: "database.db",
5
+ onQueryLog: (log) => {
6
+ console.debug(log);
7
+ },
8
+ });
9
+
10
+ db.write("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT)");
11
+
12
+ const result = db.readOne("SELECT * FROM users");
13
+
14
+ console.log(result);
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "neosqlite",
3
+ "description": "A lightweight wrapper around better-sqlite3 that adds developer-friendly features like job scheduling, migrations, error handling, query logging, SQL utilities, and more",
4
+ "version": "1.0.0",
5
+ "main": "lib/src/index.js",
6
+ "scripts": {
7
+ "build": "tsc",
8
+ "prepare": "npm run build"
9
+ },
10
+ "keywords": [
11
+ "sqlite",
12
+ "database",
13
+ "neosqlite"
14
+ ],
15
+ "author": "JS00001",
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "better-sqlite3": "^12.6.2",
19
+ "chrono-node": "^2.8.4",
20
+ "commander": "^14.0.1",
21
+ "cron-parser": "^5.4.0"
22
+ },
23
+ "devDependencies": {
24
+ "@types/better-sqlite3": "^7.6.13",
25
+ "tsx": "^4.20.5",
26
+ "typescript": "^5.9.2"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { NeosqliteClient, NeosqliteConfig } from "../types";
2
+ export declare function createClient(config: NeosqliteConfig): NeosqliteClient;
@@ -0,0 +1,62 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createClient = createClient;
7
+ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
8
+ const util_1 = require("./util");
9
+ function createClient(config) {
10
+ const { file } = config;
11
+ const db = new better_sqlite3_1.default(file);
12
+ const transaction = (fn) => {
13
+ db.transaction(() => {
14
+ fn();
15
+ });
16
+ };
17
+ /**
18
+ * Read data from the database, returns all matching rows
19
+ * @returns An array of objects
20
+ */
21
+ const read = (params) => {
22
+ const query = (0, util_1.parseQuery)(params);
23
+ const statement = db.prepare(query.sql);
24
+ return (0, util_1.runQuery)(config, params, () => statement.all(query.args));
25
+ };
26
+ /**
27
+ * Read data from the database, returns the first matching row
28
+ * @returns An object
29
+ */
30
+ const readOne = (params) => {
31
+ const query = (0, util_1.parseQuery)(params);
32
+ const statement = db.prepare(query.sql);
33
+ return (0, util_1.runQuery)(config, params, () => statement.get(query.args));
34
+ };
35
+ /**
36
+ * Write data to the database, returns the number of
37
+ * rows affected
38
+ */
39
+ const write = (params) => {
40
+ const query = (0, util_1.parseQuery)(params);
41
+ const statement = db.prepare(query.sql);
42
+ return (0, util_1.runQuery)(config, params, () => statement.run(query.args));
43
+ };
44
+ /**
45
+ * Write data to the database, returns all matching rows
46
+ * via RETURNING
47
+ */
48
+ const writeReturning = (params) => {
49
+ const query = (0, util_1.parseQuery)(params);
50
+ const statement = db.prepare(query.sql);
51
+ return (0, util_1.runQuery)(config, params, () => statement.all(query.args));
52
+ };
53
+ return {
54
+ read,
55
+ readOne,
56
+ write,
57
+ transaction,
58
+ writeReturning,
59
+ pragma: db.pragma,
60
+ backup: db.backup,
61
+ };
62
+ }
@@ -0,0 +1,108 @@
1
+ import { ExecuteQueryParams, NeosqliteConfig } from "../types";
2
+ export declare const parseQuery: (params: ExecuteQueryParams) => import("../types").QueryParams;
3
+ export declare const runQuery: (config: NeosqliteConfig, params: ExecuteQueryParams, fn: Function) => any;
4
+ /**
5
+ * Join a list of strings into a single string for
6
+ * SQL queries
7
+ *
8
+ * @example
9
+ * queryString(
10
+ * "SELECT * FROM users",
11
+ * "WHERE id = :id"
12
+ * )
13
+ * // Returns
14
+ * "SELECT * FROM users WHERE id = :id"
15
+ */
16
+ export declare const queryString: (...args: (string | boolean | undefined)[]) => string;
17
+ /**
18
+ * Take a list and return an object with its sql placeholder names, and
19
+ * its prepared arguments
20
+ *
21
+ * @example
22
+ * parameterize("id", [1, 2, 3])
23
+ * // Returns
24
+ * [
25
+ * // Placeholders
26
+ * ':id0, :id1, :id2',
27
+ * // Args
28
+ * { id0: 1, id1: 2, id2: 3 }
29
+ * ]
30
+ */
31
+ export declare const parameterizePrimitiveArray: (key: string, value: Array<string | number>) => readonly [string, {
32
+ [k: string]: string | number;
33
+ }];
34
+ /**
35
+ * Take an array of objects and extract specific fields as needed to be used
36
+ * as placeholders, and prepared arguments
37
+ *
38
+ * @example
39
+ * parameterizeComplexArray([{ id: 1, name: "foo" }, { id: 2, name: "bar" }], ["id", "name"])
40
+ * // Returns
41
+ * [
42
+ * // Placeholders
43
+ * '(:id0, :name0), (:id1, :name1)',
44
+ * // Args
45
+ * { id0: 1, name0: "foo", id1: 2, name1: "bar" }
46
+ * ]
47
+ */
48
+ export declare const parameterizeComplexArray: <T>(value: T[], fields: (keyof T)[]) => readonly [string, {}];
49
+ /**
50
+ * Sanitizes a string to be used in a SQL query
51
+ * @remarks
52
+ * You should still prefer using parameterized queries over string concatenation, but this
53
+ * is sometimes necessary
54
+ */
55
+ export declare const sanitize: (value: string | number | boolean | null | undefined) => string;
56
+ /**
57
+ * Parse a column as JSON, where it is either parsed as an object, or as
58
+ * null
59
+ *
60
+ * @returns A parsed object, or null
61
+ */
62
+ export declare const Jsonify: <T extends Record<string, any> | Array<any> = Record<string, any>>(value: string) => T | null;
63
+ /**
64
+ * Logs the full query string that will be executed.
65
+ *
66
+ * @remarks
67
+ * ⚠️ DO NOT EXECUTE THIS STRING. By default, this is for logging only, to see
68
+ * what the query would look like with its parameters in place. This does NOT
69
+ * sanitize the parameters. In fact, it unsanitizes them.
70
+ */
71
+ export declare const getFullQueryString: (params: ExecuteQueryParams) => string;
72
+ /**
73
+ * Sanitize a string to remove characters that would break glob patterns
74
+ * from the string
75
+ *
76
+ * @example
77
+ * sanitizeLike('foo%bar')
78
+ * // Returns
79
+ * 'foo\\%bar'
80
+ */
81
+ export declare const sanitizeLike: (str: string) => string;
82
+ /**
83
+ * Fully sanitize a string down to only alphanumeric characters. Useful for JSON key paths
84
+ * or other string values that cannot be parameterized, but need to be sanitized
85
+ *
86
+ * @example
87
+ * sanitizeSqlPath('foo';--bar')
88
+ * // Returns
89
+ * 'foobar'
90
+ */
91
+ export declare const sanitizeSqlPath: (str: string) => string;
92
+ /**
93
+ * Converts a date to the standard sqlite date format `YYYY-MM-DD HH:MM:SS`
94
+ * Also converts timezone to UTC
95
+ */
96
+ export declare const toSqliteDateString: (date: Date | string) => string;
97
+ /**
98
+ * Converts a database row to a String or null value
99
+ */
100
+ export declare const StringOrNull: (value: unknown) => string | null;
101
+ /**
102
+ * Converts a column value to a Number or null value
103
+ */
104
+ export declare const NumberOrNull: (value: unknown) => number | null;
105
+ /**
106
+ * Converts a column value to a Boolean or null value
107
+ */
108
+ export declare const BooleanOrNull: (value: unknown) => boolean | null;
@@ -0,0 +1,223 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BooleanOrNull = exports.NumberOrNull = exports.StringOrNull = exports.toSqliteDateString = exports.sanitizeSqlPath = exports.sanitizeLike = exports.getFullQueryString = exports.Jsonify = exports.sanitize = exports.parameterizeComplexArray = exports.parameterizePrimitiveArray = exports.queryString = exports.runQuery = exports.parseQuery = void 0;
4
+ const NEWLINE_CHAR = "\t";
5
+ const parseQuery = (params) => {
6
+ if (typeof params === "string") {
7
+ return { sql: params, args: {} };
8
+ }
9
+ return params;
10
+ };
11
+ exports.parseQuery = parseQuery;
12
+ const runQuery = (config, params, fn) => {
13
+ const startTime = process.hrtime();
14
+ const result = fn();
15
+ const endTime = process.hrtime(startTime);
16
+ const endTimeMs = endTime[0] * 1_000 + endTime[1] / 1_000_000;
17
+ if (config.onQueryComplete || config.onQueryLog) {
18
+ const queryString = (0, exports.getFullQueryString)(params);
19
+ config.onQueryComplete?.({ query: queryString, time: endTimeMs });
20
+ if (typeof params === "object" && params.logQuery) {
21
+ config.onQueryLog?.(queryString);
22
+ }
23
+ }
24
+ return result;
25
+ };
26
+ exports.runQuery = runQuery;
27
+ /**
28
+ * Join a list of strings into a single string for
29
+ * SQL queries
30
+ *
31
+ * @example
32
+ * queryString(
33
+ * "SELECT * FROM users",
34
+ * "WHERE id = :id"
35
+ * )
36
+ * // Returns
37
+ * "SELECT * FROM users WHERE id = :id"
38
+ */
39
+ const queryString = (...args) => {
40
+ const filteredArgs = args.filter(Boolean);
41
+ return filteredArgs.join(NEWLINE_CHAR);
42
+ };
43
+ exports.queryString = queryString;
44
+ /**
45
+ * Take a list and return an object with its sql placeholder names, and
46
+ * its prepared arguments
47
+ *
48
+ * @example
49
+ * parameterize("id", [1, 2, 3])
50
+ * // Returns
51
+ * [
52
+ * // Placeholders
53
+ * ':id0, :id1, :id2',
54
+ * // Args
55
+ * { id0: 1, id1: 2, id2: 3 }
56
+ * ]
57
+ */
58
+ const parameterizePrimitiveArray = (key, value) => {
59
+ const args = Object.fromEntries(value.map((v, i) => [`${key}${i}`, v]));
60
+ const placeholders = value.map((_, i) => `:${key}${i}`).join(", ");
61
+ return [placeholders, args];
62
+ };
63
+ exports.parameterizePrimitiveArray = parameterizePrimitiveArray;
64
+ /**
65
+ * Take an array of objects and extract specific fields as needed to be used
66
+ * as placeholders, and prepared arguments
67
+ *
68
+ * @example
69
+ * parameterizeComplexArray([{ id: 1, name: "foo" }, { id: 2, name: "bar" }], ["id", "name"])
70
+ * // Returns
71
+ * [
72
+ * // Placeholders
73
+ * '(:id0, :name0), (:id1, :name1)',
74
+ * // Args
75
+ * { id0: 1, name0: "foo", id1: 2, name1: "bar" }
76
+ * ]
77
+ */
78
+ const parameterizeComplexArray = (value, fields) => {
79
+ const placeholders = value.map((_, i) => `(${fields.map((field) => `:${String(field)}${i}`).join(",")})`).join(", ");
80
+ const args = value.reduce((acc, item, i) => {
81
+ return { ...acc, ...Object.fromEntries(fields.map((field) => [`${String(field)}${i}`, item[field] ?? null])) };
82
+ }, {});
83
+ return [placeholders, args];
84
+ };
85
+ exports.parameterizeComplexArray = parameterizeComplexArray;
86
+ /**
87
+ * Sanitizes a string to be used in a SQL query
88
+ * @remarks
89
+ * You should still prefer using parameterized queries over string concatenation, but this
90
+ * is sometimes necessary
91
+ */
92
+ const sanitize = (value) => {
93
+ if (value === null || value === undefined)
94
+ return "NULL";
95
+ const sanitizedString = String(value)
96
+ .replace(/\\/g, "\\\\") // backslashes
97
+ .replace(/\u0008/g, "\\b") // backspace
98
+ .replace(/\t/g, "\\t") // tab
99
+ .replace(/\n/g, "\\n") // newline
100
+ .replace(/\f/g, "\\f") // form feed
101
+ .replace(/\r/g, "\\r") // carriage return
102
+ .replace(/'/g, "''"); // single quotes
103
+ return `'${sanitizedString}'`;
104
+ };
105
+ exports.sanitize = sanitize;
106
+ /**
107
+ * Parse a column as JSON, where it is either parsed as an object, or as
108
+ * null
109
+ *
110
+ * @returns A parsed object, or null
111
+ */
112
+ const Jsonify = (value) => {
113
+ try {
114
+ return JSON.parse(value);
115
+ }
116
+ catch (err) {
117
+ return null;
118
+ }
119
+ };
120
+ exports.Jsonify = Jsonify;
121
+ /**
122
+ * Logs the full query string that will be executed.
123
+ *
124
+ * @remarks
125
+ * ⚠️ DO NOT EXECUTE THIS STRING. By default, this is for logging only, to see
126
+ * what the query would look like with its parameters in place. This does NOT
127
+ * sanitize the parameters. In fact, it unsanitizes them.
128
+ */
129
+ const getFullQueryString = (params) => {
130
+ if (typeof params === "string") {
131
+ return params;
132
+ }
133
+ let queryString = params.sql;
134
+ const args = Object.entries(params.args ?? {}).sort(([aKey], [bKey]) => {
135
+ return bKey.length - aKey.length;
136
+ });
137
+ for (const [key, value] of args) {
138
+ const regex = new RegExp(`:${key}\\b`, "g");
139
+ queryString = queryString.replace(regex, `'${value}'`);
140
+ }
141
+ let fullString = "";
142
+ const lines = queryString.split(NEWLINE_CHAR);
143
+ for (const line of lines) {
144
+ if (!line.trim())
145
+ continue;
146
+ fullString += `${line}\n`;
147
+ }
148
+ return fullString;
149
+ };
150
+ exports.getFullQueryString = getFullQueryString;
151
+ /**
152
+ * Sanitize a string to remove characters that would break glob patterns
153
+ * from the string
154
+ *
155
+ * @example
156
+ * sanitizeLike('foo%bar')
157
+ * // Returns
158
+ * 'foo\\%bar'
159
+ */
160
+ const sanitizeLike = (str) => {
161
+ return str
162
+ .replace(/\\/g, "\\\\") // escape backslashes
163
+ .replace(/%/g, "\\%") // escape %
164
+ .replace(/_/g, "\\_"); // escape _
165
+ };
166
+ exports.sanitizeLike = sanitizeLike;
167
+ /**
168
+ * Fully sanitize a string down to only alphanumeric characters. Useful for JSON key paths
169
+ * or other string values that cannot be parameterized, but need to be sanitized
170
+ *
171
+ * @example
172
+ * sanitizeSqlPath('foo';--bar')
173
+ * // Returns
174
+ * 'foobar'
175
+ */
176
+ const sanitizeSqlPath = (str) => {
177
+ return str.replace(/[^a-zA-Z0-9_]/g, "");
178
+ };
179
+ exports.sanitizeSqlPath = sanitizeSqlPath;
180
+ /**
181
+ * Converts a date to the standard sqlite date format `YYYY-MM-DD HH:MM:SS`
182
+ * Also converts timezone to UTC
183
+ */
184
+ const toSqliteDateString = (date) => {
185
+ if (typeof date === "string") {
186
+ date = new Date(date);
187
+ }
188
+ const yyyy = date.getUTCFullYear();
189
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
190
+ const dd = String(date.getUTCDate()).padStart(2, "0");
191
+ const hh = String(date.getUTCHours()).padStart(2, "0");
192
+ const min = String(date.getUTCMinutes()).padStart(2, "0");
193
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
194
+ return `${yyyy}-${mm}-${dd} ${hh}:${min}:${ss}`;
195
+ };
196
+ exports.toSqliteDateString = toSqliteDateString;
197
+ /**
198
+ * Converts a database row to a String or null value
199
+ */
200
+ const StringOrNull = (value) => {
201
+ if (value === null)
202
+ return null;
203
+ return String(value);
204
+ };
205
+ exports.StringOrNull = StringOrNull;
206
+ /**
207
+ * Converts a column value to a Number or null value
208
+ */
209
+ const NumberOrNull = (value) => {
210
+ if (value === null)
211
+ return null;
212
+ return Number(value);
213
+ };
214
+ exports.NumberOrNull = NumberOrNull;
215
+ /**
216
+ * Converts a column value to a Boolean or null value
217
+ */
218
+ const BooleanOrNull = (value) => {
219
+ if (value === null)
220
+ return null;
221
+ return Boolean(value);
222
+ };
223
+ exports.BooleanOrNull = BooleanOrNull;
@@ -0,0 +1 @@
1
+ export * from "./types";
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./types"), exports);
@@ -0,0 +1,48 @@
1
+ import { CreateJobsOptions, JobOptions } from "../types";
2
+ export declare class NeosqliteJobs {
3
+ private db;
4
+ private options;
5
+ private jobsRegistry;
6
+ private activeJobs;
7
+ private isRunning;
8
+ constructor(options: CreateJobsOptions);
9
+ private get jobsTable();
10
+ private get maxJobs();
11
+ private get processEvery();
12
+ private get maxRetries();
13
+ /**
14
+ * Setup tables and start the job worker for neosqlite jobs
15
+ */
16
+ start(): Promise<void>;
17
+ /**
18
+ * Stop the job worker from listening for jobs
19
+ */
20
+ stop(): Promise<void>;
21
+ /**
22
+ * Register a job to be run
23
+ */
24
+ register<T extends Record<string, any> = Record<string, any>>(name: string, options: JobOptions, fn: (data: T) => void, onFailure?: (data: T) => void): void;
25
+ /**
26
+ * Queue a job to run instantly based on its
27
+ * priority
28
+ */
29
+ queue<T extends Record<string, any> = Record<string, any>>(name: string, data?: T): Promise<void>;
30
+ /**
31
+ * Schedule a job to run in the future. This can use a date string, or a human-readable string
32
+ * such as 'in 10 minutes' or 'next week'
33
+ */
34
+ schedule<T extends Record<string, any> = Record<string, any>>(date: string, name: string, data?: T): Promise<void>;
35
+ /**
36
+ * Schedule a job to run every time the cron string is met
37
+ */
38
+ every<T extends Record<string, any> = Record<string, any>>(cronString: string, name: string, data?: T): Promise<void>;
39
+ /**
40
+ * Creates the jobs table and sets up indexes on it
41
+ */
42
+ private setupTables;
43
+ /**
44
+ * Sets up the jobs worker to run automatically
45
+ */
46
+ private setupJobWorker;
47
+ private processJob;
48
+ }