@rwillians/qx 0.1.20 → 0.1.21
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/cjs/adapter.js +27 -0
- package/dist/cjs/bun-sqlite.js +513 -0
- package/dist/cjs/console-logger.js +135 -0
- package/dist/cjs/experimental-migrations.js +90 -0
- package/dist/cjs/index.js +699 -0
- package/dist/cjs/standard-schema.js +239 -0
- package/dist/cjs/utils.js +92 -0
- package/dist/esm/adapter.js +23 -0
- package/dist/esm/bun-sqlite.js +512 -0
- package/dist/esm/console-logger.js +132 -0
- package/dist/esm/experimental-migrations.js +86 -0
- package/dist/esm/index.js +692 -0
- package/dist/esm/standard-schema.js +235 -0
- package/dist/esm/utils.js +79 -0
- package/dist/types/adapter.d.ts +16 -0
- package/dist/types/bun-sqlite.d.ts +59 -0
- package/dist/types/console-logger.d.ts +21 -0
- package/dist/types/experimental-migrations.d.ts +63 -0
- package/dist/types/index.d.ts +1233 -0
- package/dist/types/standard-schema.d.ts +189 -0
- package/dist/types/utils.d.ts +79 -0
- package/package.json +4 -3
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.is = exports.withLoggedQuery = void 0;
|
|
4
|
+
const u = require("./utils");
|
|
5
|
+
/**
|
|
6
|
+
* @public Wraps a function call that executes DDL with logging
|
|
7
|
+
* capabilities.
|
|
8
|
+
*
|
|
9
|
+
* A `debug` log is emitted before the function is executed,
|
|
10
|
+
* and an `error` log if the function throws an error. After
|
|
11
|
+
* logging, the error is re-thrown.
|
|
12
|
+
* @since 0.1.17
|
|
13
|
+
* @version 1
|
|
14
|
+
*/
|
|
15
|
+
const withLoggedQuery = async (logger, data, fn) => {
|
|
16
|
+
const loggers = u.wrap(logger);
|
|
17
|
+
const { sql, params } = data;
|
|
18
|
+
loggers.forEach(logger => logger.debug(sql, params));
|
|
19
|
+
return Promise.resolve().then(() => fn(sql, params)).catch(error => {
|
|
20
|
+
loggers.forEach(logger => logger.error(sql, params, error));
|
|
21
|
+
return Promise.reject(error); // propagate the error without
|
|
22
|
+
// appending stack traces
|
|
23
|
+
});
|
|
24
|
+
};
|
|
25
|
+
exports.withLoggedQuery = withLoggedQuery;
|
|
26
|
+
var index_1 = require("./index");
|
|
27
|
+
Object.defineProperty(exports, "is", { enumerable: true, get: function () { return index_1.is; } });
|
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.inmemory = exports.connect = void 0;
|
|
4
|
+
const bun_sqlite_1 = require("bun:sqlite");
|
|
5
|
+
const node_util_1 = require("node:util");
|
|
6
|
+
const u = require("./utils");
|
|
7
|
+
const adapter_1 = require("./adapter");
|
|
8
|
+
/**
|
|
9
|
+
* @private Mapping of qx's primitive types to SQLite native types.
|
|
10
|
+
* @since 0.1.0
|
|
11
|
+
* @version 1
|
|
12
|
+
*/
|
|
13
|
+
const TYPES_MAPPING = {
|
|
14
|
+
BINARY: () => 'BLOB',
|
|
15
|
+
BOOLEAN: () => 'INTEGER',
|
|
16
|
+
DATETIME: () => 'INTEGER',
|
|
17
|
+
FLOAT: () => 'REAL',
|
|
18
|
+
INTEGER: () => 'INTEGER',
|
|
19
|
+
TEXT: () => 'TEXT',
|
|
20
|
+
VARCHAR: () => `TEXT`,
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* @private Registry of codecs for Bun SQLite.
|
|
24
|
+
* @since 0.1.0
|
|
25
|
+
* @version 1
|
|
26
|
+
*/
|
|
27
|
+
const CODECS = {
|
|
28
|
+
BINARY: {
|
|
29
|
+
encode: (value) => value,
|
|
30
|
+
decode: (value) => value,
|
|
31
|
+
},
|
|
32
|
+
BOOLEAN: {
|
|
33
|
+
encode: (value) => (value ? 1 : 0),
|
|
34
|
+
decode: (value) => value === 1,
|
|
35
|
+
},
|
|
36
|
+
DATETIME: {
|
|
37
|
+
encode: (value) => value.valueOf(),
|
|
38
|
+
decode: (value) => new Date(value),
|
|
39
|
+
},
|
|
40
|
+
FLOAT: {
|
|
41
|
+
encode: (value) => value * 1.0,
|
|
42
|
+
decode: (value) => value * 1.0,
|
|
43
|
+
},
|
|
44
|
+
INTEGER: {
|
|
45
|
+
encode: (value) => ~~value, // ← nifty little trick to truncate to integer
|
|
46
|
+
decode: (value) => ~~value,
|
|
47
|
+
},
|
|
48
|
+
TEXT: {
|
|
49
|
+
encode: (value) => value,
|
|
50
|
+
decode: (value) => value,
|
|
51
|
+
},
|
|
52
|
+
VARCHAR: {
|
|
53
|
+
encode: (value) => value,
|
|
54
|
+
decode: (value) => value,
|
|
55
|
+
},
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* @private An empty rendered result.
|
|
59
|
+
* @since 0.1.0
|
|
60
|
+
* @version 1
|
|
61
|
+
*/
|
|
62
|
+
const EMPTY_RENDER_RESULT = { frags: [], params: [] };
|
|
63
|
+
/**
|
|
64
|
+
* @private A function combinator that accumulates rendered fragments
|
|
65
|
+
* and parameters when used in a reduce operation.
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
* @version 1
|
|
68
|
+
*/
|
|
69
|
+
const collect = (fn) => (acc, value) => {
|
|
70
|
+
const result = fn(value);
|
|
71
|
+
return { frags: [...acc.frags, ...result.frags], params: [...acc.params, ...result.params] };
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* @private A function combinator that glues rendered fragments
|
|
75
|
+
* together.
|
|
76
|
+
* @since 0.1.0
|
|
77
|
+
* @version 1
|
|
78
|
+
*/
|
|
79
|
+
const glue = (fn) => (...args) => {
|
|
80
|
+
const result = fn(...args);
|
|
81
|
+
return { frags: [result.frags.join('')], params: result.params };
|
|
82
|
+
};
|
|
83
|
+
/**
|
|
84
|
+
* @private Functions for rendering fragments while generateing DDL.
|
|
85
|
+
* @since 0.1.0
|
|
86
|
+
* @version 1
|
|
87
|
+
*/
|
|
88
|
+
const render = {
|
|
89
|
+
/**
|
|
90
|
+
* @private Renders an object reference (e.g., table name, column
|
|
91
|
+
* name, etc).
|
|
92
|
+
* @since 0.1.0
|
|
93
|
+
* @version 1
|
|
94
|
+
*/
|
|
95
|
+
ref: (value) => `"${value}"`,
|
|
96
|
+
/**
|
|
97
|
+
* @private Renders a column definition, for the create table
|
|
98
|
+
* statement.
|
|
99
|
+
* @since 0.1.0
|
|
100
|
+
* @version 1
|
|
101
|
+
*/
|
|
102
|
+
column: glue((col) => {
|
|
103
|
+
const { name, primaryKey = false, autoincrement = false, nullable = false, unique = false, } = col;
|
|
104
|
+
const type = TYPES_MAPPING[col.type](col);
|
|
105
|
+
// @TODO DDL should not have to know that it needs to snake_case here
|
|
106
|
+
const frags = [render.ref(u.snakeCase(name)), type];
|
|
107
|
+
if (primaryKey)
|
|
108
|
+
frags.push('PRIMARY KEY ASC');
|
|
109
|
+
if (autoincrement)
|
|
110
|
+
frags.push('AUTOINCREMENT');
|
|
111
|
+
if (primaryKey)
|
|
112
|
+
return { frags: [frags.join(' ')], params: [] };
|
|
113
|
+
if (!nullable)
|
|
114
|
+
frags.push('NOT NULL');
|
|
115
|
+
if (unique)
|
|
116
|
+
frags.push('UNIQUE');
|
|
117
|
+
return { frags: [frags.join(' ')], params: [] };
|
|
118
|
+
}),
|
|
119
|
+
/**
|
|
120
|
+
* @private Renders a value placeholder for an insert statement.
|
|
121
|
+
* @since 0.1.0
|
|
122
|
+
* @version 1
|
|
123
|
+
*/
|
|
124
|
+
row: (shape) => glue((record) => {
|
|
125
|
+
const frags = [];
|
|
126
|
+
const params = [];
|
|
127
|
+
for (const key of Object.keys(shape)) {
|
|
128
|
+
frags.push('?');
|
|
129
|
+
params.push(record[key]);
|
|
130
|
+
}
|
|
131
|
+
return { frags: ['(' + frags.join(', ') + ')'], params };
|
|
132
|
+
}),
|
|
133
|
+
/**
|
|
134
|
+
* @private Renders the columns of a query's selection.
|
|
135
|
+
* @since 0.1.0
|
|
136
|
+
* @version 1
|
|
137
|
+
*/
|
|
138
|
+
selection: glue((selection) => {
|
|
139
|
+
const frags = Object
|
|
140
|
+
.entries(selection)
|
|
141
|
+
// @TODO DDL should not have to know that it needs to snake_case here
|
|
142
|
+
.map(([alias, col]) => `${render.expr.column(col).frags.join('')} AS ${render.ref(alias)}`);
|
|
143
|
+
return { frags: [frags.join(', ')], params: [] };
|
|
144
|
+
}),
|
|
145
|
+
/**
|
|
146
|
+
* @private Renders an order by clause.
|
|
147
|
+
* @since 0.1.0
|
|
148
|
+
* @version 1
|
|
149
|
+
*
|
|
150
|
+
* Not using {@link glue} here becuase it's breaking typescript
|
|
151
|
+
* ¯\_(ツ)_/¯
|
|
152
|
+
*/
|
|
153
|
+
orderBy: ([expr, dir]) => {
|
|
154
|
+
const { frags, params } = render.expr.any(expr);
|
|
155
|
+
return { frags: [[...frags, dir].join(' ')], params };
|
|
156
|
+
},
|
|
157
|
+
/**
|
|
158
|
+
* @private Renders a join clause.
|
|
159
|
+
* @since 0.1.9
|
|
160
|
+
* @version 1
|
|
161
|
+
*/
|
|
162
|
+
join: glue((join) => {
|
|
163
|
+
const result = render.expr.any(join.on);
|
|
164
|
+
return {
|
|
165
|
+
frags: [' ', join.type, ' ', render.ref(join.table), ' AS ', render.ref(join.alias), ' ON ', ...result.frags],
|
|
166
|
+
params: result.params,
|
|
167
|
+
};
|
|
168
|
+
}),
|
|
169
|
+
/**
|
|
170
|
+
* @private Expression rendering functions.
|
|
171
|
+
* @since 0.1.0
|
|
172
|
+
* @version 1
|
|
173
|
+
*/
|
|
174
|
+
expr: {
|
|
175
|
+
/**
|
|
176
|
+
* @private Renders any {@link Expr}.
|
|
177
|
+
* @since 0.1.0
|
|
178
|
+
* @version 1
|
|
179
|
+
*/
|
|
180
|
+
any: glue((value) => {
|
|
181
|
+
if (adapter_1.is.binaryOp(value))
|
|
182
|
+
return render.expr.binaryOp(value);
|
|
183
|
+
if (adapter_1.is.and(value))
|
|
184
|
+
return render.expr.and(value);
|
|
185
|
+
if (adapter_1.is.or(value))
|
|
186
|
+
return render.expr.or(value);
|
|
187
|
+
if (adapter_1.is.not(value))
|
|
188
|
+
return render.expr.not(value);
|
|
189
|
+
if (adapter_1.is.column(value))
|
|
190
|
+
return render.expr.column(value);
|
|
191
|
+
if (adapter_1.is.literal(value))
|
|
192
|
+
return render.expr.literal(value);
|
|
193
|
+
throw new Error(`Unsupported expression type: ${(0, node_util_1.inspect)(value)}`);
|
|
194
|
+
}),
|
|
195
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
196
|
+
// BINARY OP EXPRESSIONS //
|
|
197
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
198
|
+
/**
|
|
199
|
+
* @private Renders any binary op expression.
|
|
200
|
+
* @since 0.1.0
|
|
201
|
+
* @version 1
|
|
202
|
+
*/
|
|
203
|
+
binaryOp: glue((value) => {
|
|
204
|
+
const lhs = render.expr.any(value.lhs);
|
|
205
|
+
const rhs = Array.isArray(value.rhs)
|
|
206
|
+
? render.expr.array(value.rhs)
|
|
207
|
+
: render.expr.any(value.rhs);
|
|
208
|
+
return { frags: ['(', ...lhs.frags, ` ${value.op} `, ...rhs.frags, ')'], params: [...lhs.params, ...rhs.params] };
|
|
209
|
+
}),
|
|
210
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
211
|
+
// BOOLEAN OP EXPRESSIONS //
|
|
212
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
213
|
+
/**
|
|
214
|
+
* @private Renders an AND expression.
|
|
215
|
+
* @since 0.1.0
|
|
216
|
+
* @version 1
|
|
217
|
+
*/
|
|
218
|
+
and: glue((value) => {
|
|
219
|
+
const inner = value.and.reduce(collect(render.expr.any), EMPTY_RENDER_RESULT);
|
|
220
|
+
return { frags: ['(', inner.frags.join(' AND '), ')'], params: inner.params };
|
|
221
|
+
}),
|
|
222
|
+
/**
|
|
223
|
+
* @private Renders an OR expression.
|
|
224
|
+
* @since 0.1.0
|
|
225
|
+
* @version 1
|
|
226
|
+
*/
|
|
227
|
+
or: glue((value) => {
|
|
228
|
+
const inner = value.or.reduce(collect(render.expr.any), EMPTY_RENDER_RESULT);
|
|
229
|
+
return { frags: ['(', inner.frags.join(' OR '), ')'], params: inner.params };
|
|
230
|
+
}),
|
|
231
|
+
/**
|
|
232
|
+
* @private Renders a NOT expression.
|
|
233
|
+
* @since 0.1.0
|
|
234
|
+
* @version 1
|
|
235
|
+
*/
|
|
236
|
+
not: glue((value) => {
|
|
237
|
+
const inner = render.expr.any(value.not);
|
|
238
|
+
return { frags: ['NOT ', ...inner.frags], params: inner.params };
|
|
239
|
+
}),
|
|
240
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
241
|
+
// OTHER EXPRESSIONS //
|
|
242
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
243
|
+
array: glue((values) => {
|
|
244
|
+
const { frags, params } = values.reduce(collect(render.expr.any), EMPTY_RENDER_RESULT);
|
|
245
|
+
return { frags: ['(', frags.join(', '), ')'], params };
|
|
246
|
+
}),
|
|
247
|
+
/**
|
|
248
|
+
* @private Renders a column expression.
|
|
249
|
+
* @since 0.1.0
|
|
250
|
+
* @version 1
|
|
251
|
+
*/
|
|
252
|
+
column: glue((col) => ({
|
|
253
|
+
// @TODO DDL should not have to know that it needs to snake_case here
|
|
254
|
+
frags: [`${render.ref(col.table)}.${render.ref(u.snakeCase(col.name))}`],
|
|
255
|
+
params: [],
|
|
256
|
+
})),
|
|
257
|
+
/**
|
|
258
|
+
* @private Renders a literal expression.
|
|
259
|
+
* @since 0.1.0
|
|
260
|
+
* @version 1
|
|
261
|
+
*/
|
|
262
|
+
literal: glue((value) => {
|
|
263
|
+
if (adapter_1.is.null(value))
|
|
264
|
+
return render.expr.null();
|
|
265
|
+
if (adapter_1.is.boolean(value))
|
|
266
|
+
return render.expr.boolean(value);
|
|
267
|
+
if (adapter_1.is.date(value))
|
|
268
|
+
return render.expr.date(value);
|
|
269
|
+
if (adapter_1.is.number(value))
|
|
270
|
+
return render.expr.number(value);
|
|
271
|
+
if (adapter_1.is.string(value))
|
|
272
|
+
return render.expr.string(value);
|
|
273
|
+
throw new Error(`Unsupported literal expression: ${(0, node_util_1.inspect)(value)}`);
|
|
274
|
+
}),
|
|
275
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
276
|
+
// LITERALS //
|
|
277
|
+
// // // // // // // // // // // // // // // // // // // // // //
|
|
278
|
+
/**
|
|
279
|
+
* @private Renders a boolean literal expression.
|
|
280
|
+
* @since 0.1.0
|
|
281
|
+
* @version 1
|
|
282
|
+
*/
|
|
283
|
+
boolean: glue((value) => ({
|
|
284
|
+
frags: [value ? 'TRUE' : 'FALSE'],
|
|
285
|
+
params: [],
|
|
286
|
+
})),
|
|
287
|
+
/**
|
|
288
|
+
* @private Renders a date literal expression.
|
|
289
|
+
* @since 0.1.0
|
|
290
|
+
* @version 1
|
|
291
|
+
*/
|
|
292
|
+
date: glue((value) => ({
|
|
293
|
+
frags: ['?'],
|
|
294
|
+
params: [CODECS.DATETIME.encode(value)],
|
|
295
|
+
})),
|
|
296
|
+
/**
|
|
297
|
+
* @private Renders a null literal expression.
|
|
298
|
+
* @since 0.1.0
|
|
299
|
+
* @version 1
|
|
300
|
+
*/
|
|
301
|
+
null: glue(() => ({ frags: ['NULL'], params: [] })),
|
|
302
|
+
/**
|
|
303
|
+
* @private Renders a number literal expression.
|
|
304
|
+
* @since 0.1.0
|
|
305
|
+
* @version 1
|
|
306
|
+
*/
|
|
307
|
+
number: glue((value) => ({ frags: ['?'], params: [value] })),
|
|
308
|
+
/**
|
|
309
|
+
* @private Renders a string literal expression.
|
|
310
|
+
* @since 0.1.0
|
|
311
|
+
* @version 1
|
|
312
|
+
*/
|
|
313
|
+
string: glue((value) => ({ frags: ['?'], params: [value] })),
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
/**
|
|
317
|
+
* @private DDL generation functions.
|
|
318
|
+
* @since 0.1.0
|
|
319
|
+
* @version 1
|
|
320
|
+
*/
|
|
321
|
+
const ddl = {
|
|
322
|
+
/**
|
|
323
|
+
* @private Generates DDL for create table statement.
|
|
324
|
+
* @since 0.1.0
|
|
325
|
+
* @version 1
|
|
326
|
+
*/
|
|
327
|
+
createTable: (op) => {
|
|
328
|
+
const columns = op
|
|
329
|
+
.columns
|
|
330
|
+
.map(col => render.column(col).frags.join(''))
|
|
331
|
+
.join(', ');
|
|
332
|
+
const frags = [
|
|
333
|
+
'CREATE ',
|
|
334
|
+
(op.unlogged ? 'UNLOGGED ' : ''),
|
|
335
|
+
'TABLE ',
|
|
336
|
+
(op.ifNotExists ? 'IF NOT EXISTS ' : ''),
|
|
337
|
+
render.ref(op.table),
|
|
338
|
+
' (',
|
|
339
|
+
...columns,
|
|
340
|
+
');',
|
|
341
|
+
];
|
|
342
|
+
return { sql: frags.join(''), params: [] };
|
|
343
|
+
},
|
|
344
|
+
/**
|
|
345
|
+
* @private Generates DDL for insert statement.
|
|
346
|
+
* @since 0.1.0
|
|
347
|
+
* @version 1
|
|
348
|
+
*/
|
|
349
|
+
insert: (op) => {
|
|
350
|
+
const values = op
|
|
351
|
+
.records
|
|
352
|
+
.reduce(collect(render.row(op.insertShape)), EMPTY_RENDER_RESULT);
|
|
353
|
+
const frags = [
|
|
354
|
+
'INSERT INTO ',
|
|
355
|
+
render.ref(op.table),
|
|
356
|
+
' (',
|
|
357
|
+
Object.keys(op.insertShape).map(render.ref).join(', '),
|
|
358
|
+
') VALUES ',
|
|
359
|
+
values.frags.join(', '),
|
|
360
|
+
' RETURNING ',
|
|
361
|
+
render.selection(op.returnShape).frags.join(', '),
|
|
362
|
+
';'
|
|
363
|
+
];
|
|
364
|
+
return { sql: frags.join(''), params: values.params };
|
|
365
|
+
},
|
|
366
|
+
/**
|
|
367
|
+
* @private Generates DDL for select statement.
|
|
368
|
+
* @since 0.1.0
|
|
369
|
+
* @version 1
|
|
370
|
+
*/
|
|
371
|
+
select: (op) => {
|
|
372
|
+
const joins = op.joins && op.joins.length > 0
|
|
373
|
+
? op.joins.reduce(collect(render.join), EMPTY_RENDER_RESULT)
|
|
374
|
+
: EMPTY_RENDER_RESULT;
|
|
375
|
+
const where = op.where
|
|
376
|
+
? render.expr.any(op.where)
|
|
377
|
+
: EMPTY_RENDER_RESULT;
|
|
378
|
+
const orderBy = op.orderBy && op.orderBy.length > 0
|
|
379
|
+
? op.orderBy.reduce(collect(render.orderBy), EMPTY_RENDER_RESULT)
|
|
380
|
+
: EMPTY_RENDER_RESULT;
|
|
381
|
+
const limit = op.limit !== undefined
|
|
382
|
+
? { frags: [' LIMIT ', '?'], params: [op.limit] }
|
|
383
|
+
: EMPTY_RENDER_RESULT;
|
|
384
|
+
const offset = op.offset !== undefined
|
|
385
|
+
? { frags: [' OFFSET ', '?'], params: [op.offset] }
|
|
386
|
+
: EMPTY_RENDER_RESULT;
|
|
387
|
+
const frags = [
|
|
388
|
+
'SELECT ',
|
|
389
|
+
render.selection(op.select).frags.join(', '),
|
|
390
|
+
' FROM ',
|
|
391
|
+
render.ref(op.registry[op.from]),
|
|
392
|
+
' AS ',
|
|
393
|
+
render.ref(op.from),
|
|
394
|
+
...joins.frags,
|
|
395
|
+
(where.frags.length > 0 ? ' WHERE ' : ''),
|
|
396
|
+
...where.frags,
|
|
397
|
+
(orderBy.frags.length > 0 ? ' ORDER BY ' + orderBy.frags.join(', ') : ''),
|
|
398
|
+
...limit.frags,
|
|
399
|
+
...offset.frags,
|
|
400
|
+
';'
|
|
401
|
+
];
|
|
402
|
+
return { sql: frags.join(''), params: [...joins.params, ...where.params, ...limit.params, ...offset.params] };
|
|
403
|
+
},
|
|
404
|
+
};
|
|
405
|
+
/**
|
|
406
|
+
* @private Creates an encoder function that converts a row into the
|
|
407
|
+
* expected shape and format expected by the database.
|
|
408
|
+
* @since 0.1.0
|
|
409
|
+
* @version 1
|
|
410
|
+
*/
|
|
411
|
+
const createEncoder = (shape) => {
|
|
412
|
+
const encoders = Object.fromEntries(Object
|
|
413
|
+
.entries(shape)
|
|
414
|
+
.map(([key, col]) => [key, (value) => value === null ? null : CODECS[col.type].encode(value)]));
|
|
415
|
+
return (row) => Object.fromEntries(Object
|
|
416
|
+
.entries(encoders)
|
|
417
|
+
.map(([key, encode]) => [u.snakeCase(key), encode(row[key])]));
|
|
418
|
+
};
|
|
419
|
+
/**
|
|
420
|
+
* @private Creates a decoder function that converts a database row
|
|
421
|
+
* into the shape and format expected by the application.
|
|
422
|
+
* @since 0.1.0
|
|
423
|
+
* @version 1
|
|
424
|
+
*/
|
|
425
|
+
const createDecoder = (shape) => {
|
|
426
|
+
const decoders = Object.fromEntries(Object
|
|
427
|
+
.entries(shape)
|
|
428
|
+
.map(([key, col]) => [key, (value) => value === null ? null : CODECS[col.type].decode(value)]));
|
|
429
|
+
return (row) => Object.fromEntries(Object
|
|
430
|
+
.entries(decoders)
|
|
431
|
+
.map(([key, decode]) => [key, decode(row[key])]));
|
|
432
|
+
};
|
|
433
|
+
/**
|
|
434
|
+
* @private Bun SQLite database adapter implementation.
|
|
435
|
+
* @since 0.1.0
|
|
436
|
+
* @version 1
|
|
437
|
+
*/
|
|
438
|
+
class BunSQLite {
|
|
439
|
+
conn;
|
|
440
|
+
loggers;
|
|
441
|
+
constructor(conn, loggers = []) {
|
|
442
|
+
this.conn = conn;
|
|
443
|
+
this.loggers = loggers;
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* @public Attaches a logger to the database instance.
|
|
447
|
+
* @since 0.1.0
|
|
448
|
+
* @version 1
|
|
449
|
+
*/
|
|
450
|
+
attachLogger(logger) {
|
|
451
|
+
this.loggers.push(logger);
|
|
452
|
+
return this;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* @public Executes a create table statement.
|
|
456
|
+
* @since 0.1.0
|
|
457
|
+
* @version 1
|
|
458
|
+
*/
|
|
459
|
+
async createTable(op) {
|
|
460
|
+
const { sql, params } = ddl.createTable(op);
|
|
461
|
+
await (0, adapter_1.withLoggedQuery)(this.loggers, { sql, params }, () => this.conn.run(sql));
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* @public Executes an insert statement.
|
|
465
|
+
* @since 0.1.0
|
|
466
|
+
* @version 1
|
|
467
|
+
*/
|
|
468
|
+
async insert(op) {
|
|
469
|
+
const { sql, params } = ddl.insert({
|
|
470
|
+
...op,
|
|
471
|
+
records: op.records.map(createEncoder(op.insertShape)),
|
|
472
|
+
insertShape: u.mapKeys(op.insertShape, u.snakeCase),
|
|
473
|
+
});
|
|
474
|
+
const rows = await (0, adapter_1.withLoggedQuery)(this.loggers, { sql, params }, () => this.conn
|
|
475
|
+
.prepare(sql)
|
|
476
|
+
.all(...params));
|
|
477
|
+
return rows.map(createDecoder(op.returnShape));
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* @public Executes a select statement.
|
|
481
|
+
* @since 0.1.0
|
|
482
|
+
* @version 1
|
|
483
|
+
*/
|
|
484
|
+
async query(op) {
|
|
485
|
+
const { sql, params } = ddl.select(op);
|
|
486
|
+
const rows = await (0, adapter_1.withLoggedQuery)(this.loggers, { sql, params }, () => this.conn
|
|
487
|
+
.prepare(sql)
|
|
488
|
+
.all(...params));
|
|
489
|
+
return rows.map(createDecoder(op.select));
|
|
490
|
+
}
|
|
491
|
+
/**
|
|
492
|
+
* @public Executes a function within a transaction.
|
|
493
|
+
* @since 0.1.10
|
|
494
|
+
* @version 1
|
|
495
|
+
*/
|
|
496
|
+
async transaction(fn) {
|
|
497
|
+
return this.conn.transaction(fn)();
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
/**
|
|
501
|
+
* @public Creates a connection to the database.
|
|
502
|
+
* @since 0.1.0
|
|
503
|
+
* @version 1
|
|
504
|
+
*/
|
|
505
|
+
const connect = (...args) => new BunSQLite(new bun_sqlite_1.Database(...args));
|
|
506
|
+
exports.connect = connect;
|
|
507
|
+
/**
|
|
508
|
+
* @public Creates a connection to an in-memory database.
|
|
509
|
+
* @since 0.1.12
|
|
510
|
+
* @version 2
|
|
511
|
+
*/
|
|
512
|
+
const inmemory = () => connect(':memory:');
|
|
513
|
+
exports.inmemory = inmemory;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createConsoleLogger = void 0;
|
|
4
|
+
const node_util_1 = require("node:util");
|
|
5
|
+
/**
|
|
6
|
+
* @private Non-exhaustive table of ASCII codes for styled console
|
|
7
|
+
* output.
|
|
8
|
+
* @since 0.1.21
|
|
9
|
+
* @version 1
|
|
10
|
+
*/
|
|
11
|
+
const ASCII_STYLE_CODES = {
|
|
12
|
+
blue: { open: 34, close: 39 },
|
|
13
|
+
bold: { open: 1, close: 22 },
|
|
14
|
+
brightRed: { open: 91, close: 39 },
|
|
15
|
+
dim: { open: 2, close: 22 },
|
|
16
|
+
green: { open: 32, close: 39 },
|
|
17
|
+
italic: { open: 3, close: 23 },
|
|
18
|
+
red: { open: 31, close: 39 },
|
|
19
|
+
yellow: { open: 33, close: 39 },
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* @private A registry of style functions for console output, one for
|
|
23
|
+
* each style in the ASCII codes table.
|
|
24
|
+
* @since 0.1.21
|
|
25
|
+
* @version 1
|
|
26
|
+
*/
|
|
27
|
+
const s = Object.fromEntries(Object
|
|
28
|
+
.entries(ASCII_STYLE_CODES)
|
|
29
|
+
.map(([key, style]) => [key, (str) => `\u001b[${style.open}m${str.toString()}\u001b[${style.close}m`])
|
|
30
|
+
.concat([['default', (str) => str.toString()]]));
|
|
31
|
+
/**
|
|
32
|
+
* @private Split the given query into a tuple, where the first elment
|
|
33
|
+
* is the statement name (e.g. SELECT, INSERT, etc) and the
|
|
34
|
+
* second element is the rest of the query.
|
|
35
|
+
* @since 0.1.21
|
|
36
|
+
* @version 1
|
|
37
|
+
*/
|
|
38
|
+
const splitAtStatementName = (str) => [str.split(' ')[0], str.slice(str.indexOf(' '))];
|
|
39
|
+
/**
|
|
40
|
+
* @private Paints the given SQL depending on what it does (e.g.
|
|
41
|
+
* DELETEs are red, SELECTs are green, etc).
|
|
42
|
+
* @since 0.1.21
|
|
43
|
+
* @version 1
|
|
44
|
+
*/
|
|
45
|
+
const dye = (sql) => {
|
|
46
|
+
const [statement, rest] = splitAtStatementName(sql);
|
|
47
|
+
if (statement === 'INSERT')
|
|
48
|
+
return s.green(`${s.bold(statement)} ${rest}`);
|
|
49
|
+
if (statement === 'SELECT')
|
|
50
|
+
return s.blue(`${s.bold(statement)} ${rest}`);
|
|
51
|
+
if (statement === 'UPDATE')
|
|
52
|
+
return s.yellow(`${s.bold(statement)} ${rest}`);
|
|
53
|
+
if (statement === 'DELETE')
|
|
54
|
+
return s.brightRed(`${s.bold(statement)} ${rest}`);
|
|
55
|
+
return sql;
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* @private Renders a query parameter value for logging purposes.
|
|
59
|
+
* @since 0.1.21
|
|
60
|
+
* @version 1
|
|
61
|
+
*/
|
|
62
|
+
const render = (value) => {
|
|
63
|
+
if (value === null)
|
|
64
|
+
return s.dim('null');
|
|
65
|
+
if (value === undefined)
|
|
66
|
+
return s.dim('null');
|
|
67
|
+
if (typeof value === 'boolean')
|
|
68
|
+
return value.toString();
|
|
69
|
+
if (typeof value === 'number')
|
|
70
|
+
return s.blue(value.toString());
|
|
71
|
+
if (typeof value === 'string')
|
|
72
|
+
return [s.dim('`'), value, s.dim('`')].join('');
|
|
73
|
+
if (value instanceof Date)
|
|
74
|
+
return s.blue(`${value.toISOString()}`);
|
|
75
|
+
if (Array.isArray(value))
|
|
76
|
+
return [s.dim('['), value.map(render).join(s.dim(', ')), s.dim(']')].join('');
|
|
77
|
+
throw new Error(`Unable to render value: ${(0, node_util_1.inspect)(value, true, null, true)}`);
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* @private A function that [p]retty-[p]rints the given SQL query, its
|
|
81
|
+
* parameters and its error.
|
|
82
|
+
* @since 0.1.21
|
|
83
|
+
* @version 1
|
|
84
|
+
*/
|
|
85
|
+
const pp = (sql, params, error) => [
|
|
86
|
+
dye(sql),
|
|
87
|
+
' ',
|
|
88
|
+
render(params),
|
|
89
|
+
error
|
|
90
|
+
? ('\n\n' + s.red(`${s.bold(error.constructor.name)} ${error.message}\n${error.stack}`.trim()) + '\n\n')
|
|
91
|
+
: '',
|
|
92
|
+
].join('');
|
|
93
|
+
/**
|
|
94
|
+
* @private A function that prints the given SQL query, its parameters
|
|
95
|
+
* and its error as [p]lain [t]ext (no ASCII styling).
|
|
96
|
+
* @since 0.1.21
|
|
97
|
+
* @version 1
|
|
98
|
+
*/
|
|
99
|
+
const pt = (sql, params, error) => [
|
|
100
|
+
sql,
|
|
101
|
+
' ',
|
|
102
|
+
render(params),
|
|
103
|
+
error
|
|
104
|
+
? ('\n\n' + (`${error.constructor.name} ${error.message}\n${error.stack}`.trim()) + '\n\n')
|
|
105
|
+
: '',
|
|
106
|
+
].join('');
|
|
107
|
+
/**
|
|
108
|
+
* @private Returns a function that pretty-prints its given arguments
|
|
109
|
+
* to the specified stream.
|
|
110
|
+
* @since 0.1.21
|
|
111
|
+
* @version 1
|
|
112
|
+
*/
|
|
113
|
+
const handler = (stream, fn) => (...args) => { stream.write(fn(...args)); };
|
|
114
|
+
/**
|
|
115
|
+
* @public Creates a basic console logger that pretty-prints queries.
|
|
116
|
+
*
|
|
117
|
+
* Pretty printing is enabled by default but you can disable
|
|
118
|
+
* it to print plain-text instead (no ASCII styling), just
|
|
119
|
+
* set the option `pretty` to `false`.
|
|
120
|
+
* @since 0.1.0
|
|
121
|
+
* @version 3
|
|
122
|
+
*
|
|
123
|
+
* @example
|
|
124
|
+
* ```ts
|
|
125
|
+
* import { createConsoleLogger } from '@rwillians/qx/console-logger';
|
|
126
|
+
*
|
|
127
|
+
* const prettyLogger = createConsoleLogger();
|
|
128
|
+
* const plainLogger = createConsoleLogger({ pretty: false });
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
const createConsoleLogger = (opts = { pretty: true }) => ({
|
|
132
|
+
debug: handler(process.stdout, opts.pretty ? pp : pt),
|
|
133
|
+
error: handler(process.stderr, opts.pretty ? pp : pt),
|
|
134
|
+
});
|
|
135
|
+
exports.createConsoleLogger = createConsoleLogger;
|