@truto/sqlite-builder 1.0.0 โ 1.0.1
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/README.md +8 -20
- package/dist/index.js +8 -147
- package/dist/index.js.map +4 -5
- package/dist/sql.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://badge.fury.io/js/%40truto%2Fsqlite-builder)
|
|
4
4
|
[](https://github.com/trutohq/truto-sqlite-builder/actions/workflows/ci.yml)
|
|
5
|
-
[](https://codecov.io/gh/trutohq/truto-sqlite-builder)
|
|
6
5
|
[](https://www.typescriptlang.org/)
|
|
7
6
|
[](https://opensource.org/licenses/MIT)
|
|
8
7
|
|
|
9
8
|
**Safe, zero-dependency template-literal tag for SQLite queries in any JS environment.**
|
|
10
9
|
|
|
11
|
-
|
|
10
|
+
`@truto/sqlite-builder` provides a secure and ergonomic way to build SQLite queries using tagged template literals. It prevents SQL injection attacks through parameterized queries while offering convenient helper functions for common SQL patterns.
|
|
12
11
|
|
|
13
12
|
## โจ Features
|
|
14
13
|
|
|
@@ -23,26 +22,26 @@
|
|
|
23
22
|
## ๐ฆ Installation
|
|
24
23
|
|
|
25
24
|
```bash
|
|
26
|
-
bun add truto
|
|
25
|
+
bun add @truto/sqlite-builder
|
|
27
26
|
```
|
|
28
27
|
|
|
29
28
|
```bash
|
|
30
|
-
npm install truto
|
|
29
|
+
npm install @truto/sqlite-builder
|
|
31
30
|
```
|
|
32
31
|
|
|
33
32
|
```bash
|
|
34
|
-
yarn add truto
|
|
33
|
+
yarn add @truto/sqlite-builder
|
|
35
34
|
```
|
|
36
35
|
|
|
37
36
|
```bash
|
|
38
|
-
pnpm add truto
|
|
37
|
+
pnpm add @truto/sqlite-builder
|
|
39
38
|
```
|
|
40
39
|
|
|
41
40
|
## ๐ Quick Start
|
|
42
41
|
|
|
43
42
|
```typescript
|
|
44
43
|
import sqlite3 from 'better-sqlite3'
|
|
45
|
-
import { sql } from 'truto
|
|
44
|
+
import { sql } from '@truto/sqlite-builder'
|
|
46
45
|
|
|
47
46
|
const db = new sqlite3('database.db')
|
|
48
47
|
|
|
@@ -178,7 +177,7 @@ const query = sql`SELECT * FROM users WHERE ${sql.join(conditions, ' AND ')}`
|
|
|
178
177
|
- **SQL Injection**: All interpolated values are parameterized
|
|
179
178
|
- **Stacked Queries**: Queries containing `;` followed by additional SQL are rejected
|
|
180
179
|
- **Identifier Safety**: `sql.ident()` validates against ANSI identifier rules
|
|
181
|
-
- **Length Limits**: Queries exceeding 100KB are rejected
|
|
180
|
+
- **Length Limits**: Queries exceeding 100KB are rejected
|
|
182
181
|
|
|
183
182
|
### What's Your Responsibility
|
|
184
183
|
|
|
@@ -213,7 +212,7 @@ sql`SELECT * FROM users WHERE id = ${Symbol('test')}` // Unsupported type
|
|
|
213
212
|
### Basic CRUD Operations
|
|
214
213
|
|
|
215
214
|
```typescript
|
|
216
|
-
import { sql } from 'truto
|
|
215
|
+
import { sql } from '@truto/sqlite-builder'
|
|
217
216
|
|
|
218
217
|
// CREATE with array identifiers
|
|
219
218
|
const insertColumns = ['name', 'email', 'age']
|
|
@@ -395,17 +394,6 @@ insertUsers([
|
|
|
395
394
|
])
|
|
396
395
|
```
|
|
397
396
|
|
|
398
|
-
## โ๏ธ Configuration
|
|
399
|
-
|
|
400
|
-
### Environment Variables
|
|
401
|
-
|
|
402
|
-
- `TRUTO_SQL_MAX_LENGTH`: Maximum query length in bytes (default: 102400 = 100KB)
|
|
403
|
-
|
|
404
|
-
```bash
|
|
405
|
-
# Increase query size limit to 1MB
|
|
406
|
-
export TRUTO_SQL_MAX_LENGTH=1048576
|
|
407
|
-
```
|
|
408
|
-
|
|
409
397
|
## ๐งช Testing
|
|
410
398
|
|
|
411
399
|
```bash
|
package/dist/index.js
CHANGED
|
@@ -1,143 +1,5 @@
|
|
|
1
|
-
// node:process
|
|
2
|
-
var C = Object.create;
|
|
3
|
-
var T = Object.defineProperty;
|
|
4
|
-
var q = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var A = Object.getOwnPropertyNames;
|
|
6
|
-
var I = Object.getPrototypeOf;
|
|
7
|
-
var Q = Object.prototype.hasOwnProperty;
|
|
8
|
-
var S = (e, t) => () => (t || e((t = { exports: {} }).exports, t), t.exports);
|
|
9
|
-
var N = (e, t) => {
|
|
10
|
-
for (var n in t)
|
|
11
|
-
T(e, n, { get: t[n], enumerable: true });
|
|
12
|
-
};
|
|
13
|
-
var d = (e, t, n, w) => {
|
|
14
|
-
if (t && typeof t == "object" || typeof t == "function")
|
|
15
|
-
for (let l of A(t))
|
|
16
|
-
!Q.call(e, l) && l !== n && T(e, l, { get: () => t[l], enumerable: !(w = q(t, l)) || w.enumerable });
|
|
17
|
-
return e;
|
|
18
|
-
};
|
|
19
|
-
var h = (e, t, n) => (d(e, t, "default"), n && d(n, t, "default"));
|
|
20
|
-
var y = (e, t, n) => (n = e != null ? C(I(e)) : {}, d(t || !e || !e.__esModule ? T(n, "default", { value: e, enumerable: true }) : n, e));
|
|
21
|
-
var v = S((B, E) => {
|
|
22
|
-
var r = E.exports = {}, i, u;
|
|
23
|
-
function p() {
|
|
24
|
-
throw new Error("setTimeout has not been defined");
|
|
25
|
-
}
|
|
26
|
-
function g() {
|
|
27
|
-
throw new Error("clearTimeout has not been defined");
|
|
28
|
-
}
|
|
29
|
-
(function() {
|
|
30
|
-
try {
|
|
31
|
-
typeof setTimeout == "function" ? i = setTimeout : i = p;
|
|
32
|
-
} catch {
|
|
33
|
-
i = p;
|
|
34
|
-
}
|
|
35
|
-
try {
|
|
36
|
-
typeof clearTimeout == "function" ? u = clearTimeout : u = g;
|
|
37
|
-
} catch {
|
|
38
|
-
u = g;
|
|
39
|
-
}
|
|
40
|
-
})();
|
|
41
|
-
function b(e) {
|
|
42
|
-
if (i === setTimeout)
|
|
43
|
-
return setTimeout(e, 0);
|
|
44
|
-
if ((i === p || !i) && setTimeout)
|
|
45
|
-
return i = setTimeout, setTimeout(e, 0);
|
|
46
|
-
try {
|
|
47
|
-
return i(e, 0);
|
|
48
|
-
} catch {
|
|
49
|
-
try {
|
|
50
|
-
return i.call(null, e, 0);
|
|
51
|
-
} catch {
|
|
52
|
-
return i.call(this, e, 0);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
function O(e) {
|
|
57
|
-
if (u === clearTimeout)
|
|
58
|
-
return clearTimeout(e);
|
|
59
|
-
if ((u === g || !u) && clearTimeout)
|
|
60
|
-
return u = clearTimeout, clearTimeout(e);
|
|
61
|
-
try {
|
|
62
|
-
return u(e);
|
|
63
|
-
} catch {
|
|
64
|
-
try {
|
|
65
|
-
return u.call(null, e);
|
|
66
|
-
} catch {
|
|
67
|
-
return u.call(this, e);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
var o = [], s = false, a, m = -1;
|
|
72
|
-
function U() {
|
|
73
|
-
!s || !a || (s = false, a.length ? o = a.concat(o) : m = -1, o.length && x());
|
|
74
|
-
}
|
|
75
|
-
function x() {
|
|
76
|
-
if (!s) {
|
|
77
|
-
var e = b(U);
|
|
78
|
-
s = true;
|
|
79
|
-
for (var t = o.length;t; ) {
|
|
80
|
-
for (a = o, o = [];++m < t; )
|
|
81
|
-
a && a[m].run();
|
|
82
|
-
m = -1, t = o.length;
|
|
83
|
-
}
|
|
84
|
-
a = null, s = false, O(e);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
r.nextTick = function(e) {
|
|
88
|
-
var t = new Array(arguments.length - 1);
|
|
89
|
-
if (arguments.length > 1)
|
|
90
|
-
for (var n = 1;n < arguments.length; n++)
|
|
91
|
-
t[n - 1] = arguments[n];
|
|
92
|
-
o.push(new L(e, t)), o.length === 1 && !s && b(x);
|
|
93
|
-
};
|
|
94
|
-
function L(e, t) {
|
|
95
|
-
this.fun = e, this.array = t;
|
|
96
|
-
}
|
|
97
|
-
L.prototype.run = function() {
|
|
98
|
-
this.fun.apply(null, this.array);
|
|
99
|
-
};
|
|
100
|
-
r.title = "browser";
|
|
101
|
-
r.browser = true;
|
|
102
|
-
r.env = {};
|
|
103
|
-
r.argv = [];
|
|
104
|
-
r.version = "";
|
|
105
|
-
r.versions = {};
|
|
106
|
-
function c() {}
|
|
107
|
-
r.on = c;
|
|
108
|
-
r.addListener = c;
|
|
109
|
-
r.once = c;
|
|
110
|
-
r.off = c;
|
|
111
|
-
r.removeListener = c;
|
|
112
|
-
r.removeAllListeners = c;
|
|
113
|
-
r.emit = c;
|
|
114
|
-
r.prependListener = c;
|
|
115
|
-
r.prependOnceListener = c;
|
|
116
|
-
r.listeners = function(e) {
|
|
117
|
-
return [];
|
|
118
|
-
};
|
|
119
|
-
r.binding = function(e) {
|
|
120
|
-
throw new Error("process.binding is not supported");
|
|
121
|
-
};
|
|
122
|
-
r.cwd = function() {
|
|
123
|
-
return "/";
|
|
124
|
-
};
|
|
125
|
-
r.chdir = function(e) {
|
|
126
|
-
throw new Error("process.chdir is not supported");
|
|
127
|
-
};
|
|
128
|
-
r.umask = function() {
|
|
129
|
-
return 0;
|
|
130
|
-
};
|
|
131
|
-
});
|
|
132
|
-
var f = {};
|
|
133
|
-
N(f, { default: () => j });
|
|
134
|
-
h(f, y(v()));
|
|
135
|
-
var j = y(v());
|
|
136
|
-
|
|
137
1
|
// src/sql.ts
|
|
138
|
-
|
|
139
|
-
return parseInt(j.env.TRUTO_SQL_MAX_LENGTH || "102400", 10);
|
|
140
|
-
}
|
|
2
|
+
var MAX_QUERY_LENGTH = 102400;
|
|
141
3
|
var STACKED_QUERY_REGEX = /;[\s\S]*\S/;
|
|
142
4
|
var QUALIFIED_IDENTIFIER_REGEX = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)*$/;
|
|
143
5
|
function formatDate(date) {
|
|
@@ -191,8 +53,8 @@ function sqlIdent(identifier) {
|
|
|
191
53
|
throw new TypeError("Array items must be strings or SQL fragments");
|
|
192
54
|
}
|
|
193
55
|
}
|
|
194
|
-
const text = fragments.map((
|
|
195
|
-
const values = fragments.flatMap((
|
|
56
|
+
const text = fragments.map((f) => f.text).join(", ");
|
|
57
|
+
const values = fragments.flatMap((f) => [...f.values]);
|
|
196
58
|
return {
|
|
197
59
|
text,
|
|
198
60
|
values
|
|
@@ -251,8 +113,8 @@ function sqlJoin(fragments, separator = ", ") {
|
|
|
251
113
|
if (fragments.length === 0) {
|
|
252
114
|
return { text: "", values: [] };
|
|
253
115
|
}
|
|
254
|
-
const text = fragments.map((
|
|
255
|
-
const values = fragments.flatMap((
|
|
116
|
+
const text = fragments.map((f) => f.text).join(separator);
|
|
117
|
+
const values = fragments.flatMap((f) => [...f.values]);
|
|
256
118
|
return { text, values };
|
|
257
119
|
}
|
|
258
120
|
function sql(strings, ...values) {
|
|
@@ -270,9 +132,8 @@ function sql(strings, ...values) {
|
|
|
270
132
|
}
|
|
271
133
|
text += strings[i + 1] || "";
|
|
272
134
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
throw new Error(`Query too long: ${text.length} bytes (max: ${maxLength})`);
|
|
135
|
+
if (text.length > MAX_QUERY_LENGTH) {
|
|
136
|
+
throw new Error(`Query too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`);
|
|
276
137
|
}
|
|
277
138
|
if (STACKED_QUERY_REGEX.test(text)) {
|
|
278
139
|
throw new Error("Stacked queries are not allowed");
|
|
@@ -292,5 +153,5 @@ export {
|
|
|
292
153
|
sql
|
|
293
154
|
};
|
|
294
155
|
|
|
295
|
-
//# debugId=
|
|
156
|
+
//# debugId=85D99CAC0423D74264756E2164756E21
|
|
296
157
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["
|
|
3
|
+
"sources": ["../src/sql.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
-
"
|
|
6
|
-
"import process from 'node:process'\nimport type { SqlFragment, SqlQuery, SqlValue } from './types'\n\n// Get max query length from environment (default 100KB)\nfunction getMaxQueryLength(): number {\n return parseInt(process.env.TRUTO_SQL_MAX_LENGTH || '102400', 10)\n}\n\n// Regex to detect stacked queries (semicolon followed by non-whitespace)\nconst STACKED_QUERY_REGEX = /;[\\s\\S]*\\S/\n\n// ANSI identifier validation - supports qualified identifiers (e.g., table.column)\nconst QUALIFIED_IDENTIFIER_REGEX =\n /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/\n\n/**\n * Format a date for SQLite (YYYY-MM-DD HH:MM:SS)\n */\nfunction formatDate(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n const seconds = String(date.getSeconds()).padStart(2, '0')\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\n/**\n * Convert a value to its SQLite representation\n */\nfunction sqlValue(value: SqlValue): unknown {\n if (value === null || value === undefined) {\n return null\n }\n\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return value\n }\n\n if (value instanceof Date) {\n return formatDate(value)\n }\n\n if (value instanceof Buffer || value instanceof Uint8Array) {\n throw new TypeError(\n 'Buffer/Uint8Array values must be used with sql.blob() for safe BLOB handling',\n )\n }\n\n throw new TypeError(`Unsupported value type: ${typeof value}`)\n}\n\n/**\n * Validate and quote a SQL identifier or array of identifiers/fragments\n */\nfunction sqlIdent(\n identifier: string | readonly (string | SqlFragment)[],\n): SqlFragment {\n // Handle array of identifiers and fragments\n if (Array.isArray(identifier)) {\n if (identifier.length === 0) {\n throw new TypeError('Identifier array cannot be empty')\n }\n\n const fragments: SqlFragment[] = []\n\n for (const item of identifier) {\n // Handle SqlFragment objects (like sql.raw())\n if (\n item &&\n typeof item === 'object' &&\n 'text' in item &&\n 'values' in item &&\n typeof (item as Record<string, unknown>).text === 'string' &&\n Array.isArray((item as Record<string, unknown>).values)\n ) {\n fragments.push(item as SqlFragment)\n } else if (typeof item === 'string') {\n // Handle string identifiers\n if (!item) {\n throw new TypeError('All identifiers must be non-empty strings')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(item)) {\n throw new TypeError(\n `Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n fragments.push({\n text: '\"' + item + '\"',\n values: [],\n })\n } else {\n throw new TypeError('Array items must be strings or SQL fragments')\n }\n }\n\n // Join all fragments\n const text = fragments.map((f) => f.text).join(', ')\n const values = fragments.flatMap((f) => [...f.values])\n\n return {\n text,\n values,\n }\n }\n\n // Handle single identifier (existing behavior)\n if (!identifier || typeof identifier !== 'string') {\n throw new TypeError('Identifier must be a non-empty string')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n return {\n text: '\"' + identifier + '\"',\n values: [],\n }\n}\n\n/**\n * Create SQL IN clause from array\n */\nfunction sqlIn(array: readonly unknown[]): SqlFragment {\n if (!Array.isArray(array)) {\n throw new TypeError('sql.in() requires an array')\n }\n\n if (array.length === 0) {\n throw new TypeError('sql.in() cannot be used with empty arrays')\n }\n\n // Soft warning for large arrays\n if (array.length > 1000) {\n console.warn(\n `sql.in(): Large array with ${array.length} items. Consider using temporary tables for better performance.`,\n )\n }\n\n const placeholders = array.map(() => '?').join(',')\n const values = array.map(sqlValue)\n\n return {\n text: `(${placeholders})`,\n values,\n }\n}\n\n/**\n * Create raw SQL fragment (DANGEROUS - must not contain user input)\n */\nfunction sqlRaw(rawSql: string): SqlFragment {\n if (typeof rawSql !== 'string') {\n throw new TypeError('sql.raw() requires a string')\n }\n\n return {\n text: rawSql,\n values: [],\n }\n}\n\n/**\n * Create SQL fragment for BLOB data (for validated binary data)\n */\nfunction sqlBlob(data: Buffer | Uint8Array): SqlFragment {\n if (!(data instanceof Buffer) && !(data instanceof Uint8Array)) {\n throw new TypeError('sql.blob() requires a Buffer or Uint8Array')\n }\n\n return {\n text: '?',\n values: [data],\n }\n}\n\n/**\n * Join SQL fragments with a separator\n */\nfunction sqlJoin(\n fragments: readonly SqlFragment[],\n separator = ', ',\n): SqlFragment {\n if (!Array.isArray(fragments)) {\n throw new TypeError('sql.join() requires an array of fragments')\n }\n\n if (fragments.length === 0) {\n return { text: '', values: [] }\n }\n\n const text = fragments.map((f: SqlFragment) => f.text).join(separator)\n const values = fragments.flatMap((f: SqlFragment) => [...f.values])\n\n return { text, values }\n}\n\n/**\n * Main SQL tagged template function\n */\nfunction sql(strings: TemplateStringsArray, ...values: unknown[]): SqlQuery {\n // Build the query text and collect values\n let text = strings[0] || ''\n const queryValues: unknown[] = []\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i]\n\n // Handle SqlFragment objects (from helper functions)\n if (\n value &&\n typeof value === 'object' &&\n 'text' in value &&\n 'values' in value &&\n typeof (value as Record<string, unknown>).text === 'string' &&\n Array.isArray((value as Record<string, unknown>).values)\n ) {\n const fragment = value as SqlFragment\n text += fragment.text\n queryValues.push(...fragment.values)\n } else {\n // Regular value - add placeholder and collect value\n text += '?'\n queryValues.push(sqlValue(value as SqlValue))\n }\n\n text += strings[i + 1] || ''\n }\n\n // Security checks\n const maxLength = getMaxQueryLength()\n if (text.length > maxLength) {\n throw new Error(`Query too long: ${text.length} bytes (max: ${maxLength})`)\n }\n\n if (STACKED_QUERY_REGEX.test(text)) {\n throw new Error('Stacked queries are not allowed')\n }\n\n // Return frozen result\n return Object.freeze({\n text,\n values: Object.freeze([...queryValues]),\n })\n}\n\n// Attach helper functions to sql\nsql.value = sqlValue\nsql.ident = sqlIdent\nsql.in = sqlIn\nsql.raw = sqlRaw\nsql.blob = sqlBlob\nsql.join = sqlJoin\n\nexport { sql }\n"
|
|
5
|
+
"import type { SqlFragment, SqlQuery, SqlValue } from './types'\n\n// Maximum query length (100KB)\nconst MAX_QUERY_LENGTH = 102400\n\n// Regex to detect stacked queries (semicolon followed by non-whitespace)\nconst STACKED_QUERY_REGEX = /;[\\s\\S]*\\S/\n\n// ANSI identifier validation - supports qualified identifiers (e.g., table.column)\nconst QUALIFIED_IDENTIFIER_REGEX =\n /^[A-Za-z_][A-Za-z0-9_]*(\\.[A-Za-z_][A-Za-z0-9_]*)*$/\n\n/**\n * Format a date for SQLite (YYYY-MM-DD HH:MM:SS)\n */\nfunction formatDate(date: Date): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n const seconds = String(date.getSeconds()).padStart(2, '0')\n\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\n/**\n * Convert a value to its SQLite representation\n */\nfunction sqlValue(value: SqlValue): unknown {\n if (value === null || value === undefined) {\n return null\n }\n\n if (typeof value === 'string') {\n return value\n }\n\n if (typeof value === 'number' || typeof value === 'boolean') {\n return value\n }\n\n if (value instanceof Date) {\n return formatDate(value)\n }\n\n if (value instanceof Buffer || value instanceof Uint8Array) {\n throw new TypeError(\n 'Buffer/Uint8Array values must be used with sql.blob() for safe BLOB handling',\n )\n }\n\n throw new TypeError(`Unsupported value type: ${typeof value}`)\n}\n\n/**\n * Validate and quote a SQL identifier or array of identifiers/fragments\n */\nfunction sqlIdent(\n identifier: string | readonly (string | SqlFragment)[],\n): SqlFragment {\n // Handle array of identifiers and fragments\n if (Array.isArray(identifier)) {\n if (identifier.length === 0) {\n throw new TypeError('Identifier array cannot be empty')\n }\n\n const fragments: SqlFragment[] = []\n\n for (const item of identifier) {\n // Handle SqlFragment objects (like sql.raw())\n if (\n item &&\n typeof item === 'object' &&\n 'text' in item &&\n 'values' in item &&\n typeof (item as Record<string, unknown>).text === 'string' &&\n Array.isArray((item as Record<string, unknown>).values)\n ) {\n fragments.push(item as SqlFragment)\n } else if (typeof item === 'string') {\n // Handle string identifiers\n if (!item) {\n throw new TypeError('All identifiers must be non-empty strings')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(item)) {\n throw new TypeError(\n `Invalid identifier: ${item}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n fragments.push({\n text: '\"' + item + '\"',\n values: [],\n })\n } else {\n throw new TypeError('Array items must be strings or SQL fragments')\n }\n }\n\n // Join all fragments\n const text = fragments.map((f) => f.text).join(', ')\n const values = fragments.flatMap((f) => [...f.values])\n\n return {\n text,\n values,\n }\n }\n\n // Handle single identifier (existing behavior)\n if (!identifier || typeof identifier !== 'string') {\n throw new TypeError('Identifier must be a non-empty string')\n }\n\n if (!QUALIFIED_IDENTIFIER_REGEX.test(identifier)) {\n throw new TypeError(\n `Invalid identifier: ${identifier}. Must be a valid identifier or qualified identifier (e.g., table.column)`,\n )\n }\n\n return {\n text: '\"' + identifier + '\"',\n values: [],\n }\n}\n\n/**\n * Create SQL IN clause from array\n */\nfunction sqlIn(array: readonly unknown[]): SqlFragment {\n if (!Array.isArray(array)) {\n throw new TypeError('sql.in() requires an array')\n }\n\n if (array.length === 0) {\n throw new TypeError('sql.in() cannot be used with empty arrays')\n }\n\n // Soft warning for large arrays\n if (array.length > 1000) {\n console.warn(\n `sql.in(): Large array with ${array.length} items. Consider using temporary tables for better performance.`,\n )\n }\n\n const placeholders = array.map(() => '?').join(',')\n const values = array.map(sqlValue)\n\n return {\n text: `(${placeholders})`,\n values,\n }\n}\n\n/**\n * Create raw SQL fragment (DANGEROUS - must not contain user input)\n */\nfunction sqlRaw(rawSql: string): SqlFragment {\n if (typeof rawSql !== 'string') {\n throw new TypeError('sql.raw() requires a string')\n }\n\n return {\n text: rawSql,\n values: [],\n }\n}\n\n/**\n * Create SQL fragment for BLOB data (for validated binary data)\n */\nfunction sqlBlob(data: Buffer | Uint8Array): SqlFragment {\n if (!(data instanceof Buffer) && !(data instanceof Uint8Array)) {\n throw new TypeError('sql.blob() requires a Buffer or Uint8Array')\n }\n\n return {\n text: '?',\n values: [data],\n }\n}\n\n/**\n * Join SQL fragments with a separator\n */\nfunction sqlJoin(\n fragments: readonly SqlFragment[],\n separator = ', ',\n): SqlFragment {\n if (!Array.isArray(fragments)) {\n throw new TypeError('sql.join() requires an array of fragments')\n }\n\n if (fragments.length === 0) {\n return { text: '', values: [] }\n }\n\n const text = fragments.map((f: SqlFragment) => f.text).join(separator)\n const values = fragments.flatMap((f: SqlFragment) => [...f.values])\n\n return { text, values }\n}\n\n/**\n * Main SQL tagged template function\n */\nfunction sql(strings: TemplateStringsArray, ...values: unknown[]): SqlQuery {\n // Build the query text and collect values\n let text = strings[0] || ''\n const queryValues: unknown[] = []\n\n for (let i = 0; i < values.length; i++) {\n const value = values[i]\n\n // Handle SqlFragment objects (from helper functions)\n if (\n value &&\n typeof value === 'object' &&\n 'text' in value &&\n 'values' in value &&\n typeof (value as Record<string, unknown>).text === 'string' &&\n Array.isArray((value as Record<string, unknown>).values)\n ) {\n const fragment = value as SqlFragment\n text += fragment.text\n queryValues.push(...fragment.values)\n } else {\n // Regular value - add placeholder and collect value\n text += '?'\n queryValues.push(sqlValue(value as SqlValue))\n }\n\n text += strings[i + 1] || ''\n }\n\n // Security checks\n if (text.length > MAX_QUERY_LENGTH) {\n throw new Error(\n `Query too long: ${text.length} bytes (max: ${MAX_QUERY_LENGTH})`,\n )\n }\n\n if (STACKED_QUERY_REGEX.test(text)) {\n throw new Error('Stacked queries are not allowed')\n }\n\n // Return frozen result\n return Object.freeze({\n text,\n values: Object.freeze([...queryValues]),\n })\n}\n\n// Attach helper functions to sql\nsql.value = sqlValue\nsql.ident = sqlIdent\nsql.in = sqlIn\nsql.raw = sqlRaw\nsql.blob = sqlBlob\nsql.join = sqlJoin\n\nexport { sql }\n"
|
|
7
6
|
],
|
|
8
|
-
"mappings": ";
|
|
9
|
-
"debugId": "
|
|
7
|
+
"mappings": "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",
|
|
8
|
+
"debugId": "85D99CAC0423D74264756E2164756E21",
|
|
10
9
|
"names": []
|
|
11
10
|
}
|
package/dist/sql.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"sql.d.ts","sourceRoot":"","sources":["../src/sql.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AA0B9D;;GAEG;AACH,iBAAS,QAAQ,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAwB1C;AAED;;GAEG;AACH,iBAAS,QAAQ,CACf,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,GAAG,WAAW,CAAC,EAAE,GACrD,WAAW,CAkEb;AAED;;GAEG;AACH,iBAAS,KAAK,CAAC,KAAK,EAAE,SAAS,OAAO,EAAE,GAAG,WAAW,CAuBrD;AAED;;GAEG;AACH,iBAAS,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,WAAW,CAS3C;AAED;;GAEG;AACH,iBAAS,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,WAAW,CASvD;AAED;;GAEG;AACH,iBAAS,OAAO,CACd,SAAS,EAAE,SAAS,WAAW,EAAE,EACjC,SAAS,SAAO,GACf,WAAW,CAab;AAED;;GAEG;AACH,iBAAS,GAAG,CAAC,OAAO,EAAE,oBAAoB,EAAE,GAAG,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CA6C1E;kBA7CQ,GAAG;;;;;;;;;AAuDZ,OAAO,EAAE,GAAG,EAAE,CAAA"}
|