@rwillians/exto 0.1.1 → 0.2.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/README.md +207 -153
- package/dist/cjs/bun-postgres.js +100 -0
- package/dist/cjs/bun-sqlite.js +100 -0
- package/dist/cjs/index.js +712 -0
- package/dist/cjs/standard-schema.js +249 -0
- package/dist/cjs/utils.js +17 -0
- package/dist/esm/bun-postgres.js +96 -0
- package/dist/esm/bun-sqlite.js +96 -0
- package/dist/esm/index.js +663 -0
- package/dist/esm/standard-schema.js +237 -0
- package/dist/esm/utils.js +10 -0
- package/dist/types/bun-postgres.d.ts +17 -0
- package/dist/types/bun-sqlite.d.ts +17 -0
- package/dist/types/index.d.ts +438 -0
- package/dist/types/standard-schema.d.ts +180 -0
- package/dist/types/utils.d.ts +11 -0
- package/package.json +9 -8
- package/dist/.gitignore +0 -2
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
3
|
+
// STANDARD SCHEMA SPEC //
|
|
4
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.uuid = exports.strictObject = exports.string = exports.record = exports.optional = exports.number = exports.nullable = exports.isoDate = exports.integer = exports.instanceOf = exports.enumOf = exports.date = exports.boolean = exports.bigint = exports.array = exports.parseOrThrow = exports.parse = exports.ValidationError = void 0;
|
|
7
|
+
/**
|
|
8
|
+
* @public Thrown when a value fails validation, carrying every issue found.
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
* @version 1
|
|
11
|
+
*/
|
|
12
|
+
class ValidationError extends Error {
|
|
13
|
+
issues;
|
|
14
|
+
constructor(issues) {
|
|
15
|
+
super('Failed validation:\n' + issues.map(issue => ` - ${formatPath(issue.path)}${issue.message}`).join('\n'));
|
|
16
|
+
this.issues = issues;
|
|
17
|
+
this.name = 'ValidationError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
exports.ValidationError = ValidationError;
|
|
21
|
+
/**
|
|
22
|
+
* @public Use any standard schema to parse a value.
|
|
23
|
+
* @since 0.1.0
|
|
24
|
+
* @version 1
|
|
25
|
+
*/
|
|
26
|
+
const parse = (schema, value) => {
|
|
27
|
+
const parsed = schema['~standard'].validate(value);
|
|
28
|
+
if (parsed instanceof Promise) {
|
|
29
|
+
throw new Error('async standard schema validators are not supported');
|
|
30
|
+
}
|
|
31
|
+
return parsed;
|
|
32
|
+
};
|
|
33
|
+
exports.parse = parse;
|
|
34
|
+
/**
|
|
35
|
+
* @public Like {@link parse}, but returns the value or throws a
|
|
36
|
+
* {@link ValidationError}.
|
|
37
|
+
* @since 0.1.0
|
|
38
|
+
* @version 1
|
|
39
|
+
*/
|
|
40
|
+
const parseOrThrow = (schema, value) => {
|
|
41
|
+
const parsed = (0, exports.parse)(schema, value);
|
|
42
|
+
if (parsed.issues)
|
|
43
|
+
throw new ValidationError(parsed.issues);
|
|
44
|
+
return parsed.value;
|
|
45
|
+
};
|
|
46
|
+
exports.parseOrThrow = parseOrThrow;
|
|
47
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
48
|
+
// UTILS //
|
|
49
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
50
|
+
const formatPath = (path) => path && path.length > 0
|
|
51
|
+
? path.map(segment => String(typeof segment === 'object' ? segment.key : segment)).join('.') + ': '
|
|
52
|
+
: '';
|
|
53
|
+
const prependPath = (path) => (issue) => ({
|
|
54
|
+
...issue,
|
|
55
|
+
path: [path, ...(issue.path ?? [])],
|
|
56
|
+
});
|
|
57
|
+
const define = (validate) => ({
|
|
58
|
+
'~standard': { version: 1, vendor: 'exto', validate },
|
|
59
|
+
});
|
|
60
|
+
const fail = (message) => ({ issues: [{ message }] });
|
|
61
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
62
|
+
// BUILT-IN VALIDATORS //
|
|
63
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
64
|
+
/**
|
|
65
|
+
* @public Defines an array of a given standard schema.
|
|
66
|
+
* @since 0.1.0
|
|
67
|
+
* @version 1
|
|
68
|
+
*/
|
|
69
|
+
const array = (schema) => define((input) => {
|
|
70
|
+
if (!Array.isArray(input))
|
|
71
|
+
return fail('must be an array');
|
|
72
|
+
const issues = [];
|
|
73
|
+
const value = [];
|
|
74
|
+
input.forEach((item, i) => {
|
|
75
|
+
const parsed = (0, exports.parse)(schema, item);
|
|
76
|
+
if (parsed.issues)
|
|
77
|
+
issues.push(...parsed.issues.map(prependPath(i)));
|
|
78
|
+
else
|
|
79
|
+
value.push(parsed.value);
|
|
80
|
+
});
|
|
81
|
+
return issues.length > 0
|
|
82
|
+
? { issues }
|
|
83
|
+
: { value };
|
|
84
|
+
});
|
|
85
|
+
exports.array = array;
|
|
86
|
+
/**
|
|
87
|
+
* @public Defines a standard schema for bigints.
|
|
88
|
+
* @since 0.1.0
|
|
89
|
+
* @version 1
|
|
90
|
+
*/
|
|
91
|
+
const bigint = () => define((input) => typeof input === 'bigint'
|
|
92
|
+
? { value: input }
|
|
93
|
+
: fail('must be a bigint'));
|
|
94
|
+
exports.bigint = bigint;
|
|
95
|
+
/**
|
|
96
|
+
* @public Defines a standard schema for boolean values.
|
|
97
|
+
* @since 0.1.0
|
|
98
|
+
* @version 1
|
|
99
|
+
*/
|
|
100
|
+
const boolean = () => define((input) => typeof input === 'boolean'
|
|
101
|
+
? { value: input }
|
|
102
|
+
: fail('must be a boolean'));
|
|
103
|
+
exports.boolean = boolean;
|
|
104
|
+
/**
|
|
105
|
+
* @public Defines a standard schema for dates, coercing ISO 8601 strings
|
|
106
|
+
* and epoch timestamps (in milliseconds) into `Date`.
|
|
107
|
+
* @since 0.1.0
|
|
108
|
+
* @version 1
|
|
109
|
+
*/
|
|
110
|
+
const date = () => define((input) => {
|
|
111
|
+
const value = input instanceof Date ? input
|
|
112
|
+
: typeof input === 'string' || typeof input === 'number' ? new Date(input)
|
|
113
|
+
: undefined;
|
|
114
|
+
return value && !Number.isNaN(value.getTime())
|
|
115
|
+
? { value }
|
|
116
|
+
: fail('must be a valid date, ISO 8601 string or epoch timestamp in milliseconds');
|
|
117
|
+
});
|
|
118
|
+
exports.date = date;
|
|
119
|
+
/**
|
|
120
|
+
* @public Defines a standard schema for one of the given string values.
|
|
121
|
+
* @since 0.1.0
|
|
122
|
+
* @version 1
|
|
123
|
+
*/
|
|
124
|
+
const enumOf = (values) => define((input) => values.includes(input)
|
|
125
|
+
? { value: input }
|
|
126
|
+
: fail(`must be one of: ${values.join(', ')}`));
|
|
127
|
+
exports.enumOf = enumOf;
|
|
128
|
+
/**
|
|
129
|
+
* @public Defines a standard schema for instances of a given class.
|
|
130
|
+
* @since 0.1.0
|
|
131
|
+
* @version 1
|
|
132
|
+
*/
|
|
133
|
+
const instanceOf = (ctor) => define((input) => input instanceof ctor
|
|
134
|
+
? { value: input }
|
|
135
|
+
: fail(`must be an instance of ${ctor.name}`));
|
|
136
|
+
exports.instanceOf = instanceOf;
|
|
137
|
+
/**
|
|
138
|
+
* @public Defines a standard schema for integers, within the safe integer
|
|
139
|
+
* range by default.
|
|
140
|
+
* @since 0.1.0
|
|
141
|
+
* @version 1
|
|
142
|
+
*/
|
|
143
|
+
const integer = ({ min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = {}) => define((input) => {
|
|
144
|
+
if (!Number.isInteger(input))
|
|
145
|
+
return fail('must be an integer');
|
|
146
|
+
if (input < min)
|
|
147
|
+
return fail(`must be greater than or equal to ${min}`);
|
|
148
|
+
if (input > max)
|
|
149
|
+
return fail(`must be less than or equal to ${max}`);
|
|
150
|
+
return { value: input };
|
|
151
|
+
});
|
|
152
|
+
exports.integer = integer;
|
|
153
|
+
/**
|
|
154
|
+
* @public Defines a standard schema for calendar dates as `YYYY-MM-DD`
|
|
155
|
+
* strings.
|
|
156
|
+
* @since 0.1.0
|
|
157
|
+
* @version 1
|
|
158
|
+
*/
|
|
159
|
+
const isoDate = () => define((input) => {
|
|
160
|
+
const valid = typeof input === 'string'
|
|
161
|
+
&& /^\d{4}-\d{2}-\d{2}$/.test(input)
|
|
162
|
+
&& new Date(`${input}T00:00:00Z`).toISOString().startsWith(input);
|
|
163
|
+
return valid
|
|
164
|
+
? { value: input }
|
|
165
|
+
: fail('must be a valid date in the YYYY-MM-DD format');
|
|
166
|
+
});
|
|
167
|
+
exports.isoDate = isoDate;
|
|
168
|
+
/**
|
|
169
|
+
* @public Makes a given standard schema accept `null`.
|
|
170
|
+
* @since 0.1.0
|
|
171
|
+
* @version 1
|
|
172
|
+
*/
|
|
173
|
+
const nullable = (schema) => define((input) => input === null
|
|
174
|
+
? { value: null }
|
|
175
|
+
: (0, exports.parse)(schema, input));
|
|
176
|
+
exports.nullable = nullable;
|
|
177
|
+
/**
|
|
178
|
+
* @public Defines a standard schema for finite numbers.
|
|
179
|
+
* @since 0.1.0
|
|
180
|
+
* @version 1
|
|
181
|
+
*/
|
|
182
|
+
const number = () => define((input) => Number.isFinite(input)
|
|
183
|
+
? { value: input }
|
|
184
|
+
: fail('must be a finite number'));
|
|
185
|
+
exports.number = number;
|
|
186
|
+
/**
|
|
187
|
+
* @public Makes a given standard schema accept `undefined`.
|
|
188
|
+
* @since 0.1.0
|
|
189
|
+
* @version 1
|
|
190
|
+
*/
|
|
191
|
+
const optional = (schema) => define((input) => input === undefined
|
|
192
|
+
? { value: undefined }
|
|
193
|
+
: (0, exports.parse)(schema, input));
|
|
194
|
+
exports.optional = optional;
|
|
195
|
+
/**
|
|
196
|
+
* @public Defines a standard schema for plain objects with string keys.
|
|
197
|
+
* @since 0.2.0
|
|
198
|
+
* @version 1
|
|
199
|
+
*/
|
|
200
|
+
const record = () => define((input) => typeof input === 'object' && input !== null && Object.getPrototypeOf(input) === Object.prototype
|
|
201
|
+
? { value: input }
|
|
202
|
+
: fail('must be a plain object'));
|
|
203
|
+
exports.record = record;
|
|
204
|
+
/**
|
|
205
|
+
* @public Defines a standard schema for strings, optionally limited in
|
|
206
|
+
* length.
|
|
207
|
+
* @since 0.1.0
|
|
208
|
+
* @version 1
|
|
209
|
+
*/
|
|
210
|
+
const string = ({ max } = {}) => define((input) => {
|
|
211
|
+
if (typeof input !== 'string')
|
|
212
|
+
return fail('must be a string');
|
|
213
|
+
if (max !== undefined && input.length > max)
|
|
214
|
+
return fail(`must be at most ${max} characters long`);
|
|
215
|
+
return { value: input };
|
|
216
|
+
});
|
|
217
|
+
exports.string = string;
|
|
218
|
+
const strictObject = (shape) => define((input) => {
|
|
219
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
220
|
+
return fail('must be an object');
|
|
221
|
+
const record = input;
|
|
222
|
+
const issues = Object
|
|
223
|
+
.keys(record)
|
|
224
|
+
.filter(key => !(key in shape))
|
|
225
|
+
.map(key => ({ path: [key], message: 'unknown field' }));
|
|
226
|
+
const value = {};
|
|
227
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
228
|
+
const parsed = (0, exports.parse)(schema, record[key]);
|
|
229
|
+
if (parsed.issues && record[key] === undefined)
|
|
230
|
+
issues.push({ path: [key], message: 'is required' });
|
|
231
|
+
else if (parsed.issues)
|
|
232
|
+
issues.push(...parsed.issues.map(prependPath(key)));
|
|
233
|
+
else if (parsed.value !== undefined)
|
|
234
|
+
value[key] = parsed.value;
|
|
235
|
+
}
|
|
236
|
+
return issues.length > 0
|
|
237
|
+
? { issues }
|
|
238
|
+
: { value: value };
|
|
239
|
+
});
|
|
240
|
+
exports.strictObject = strictObject;
|
|
241
|
+
/**
|
|
242
|
+
* @public Defines a standard schema for UUID strings (any version).
|
|
243
|
+
* @since 0.1.0
|
|
244
|
+
* @version 1
|
|
245
|
+
*/
|
|
246
|
+
const uuid = () => define((input) => typeof input === 'string' && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(input)
|
|
247
|
+
? { value: input }
|
|
248
|
+
: fail('must be a valid UUID'));
|
|
249
|
+
exports.uuid = uuid;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.wrap = exports.snakeCase = exports.camelCase = void 0;
|
|
4
|
+
exports.raise = raise;
|
|
5
|
+
const camelCase = (str) => str.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
6
|
+
exports.camelCase = camelCase;
|
|
7
|
+
const snakeCase = (str) => str.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
|
|
8
|
+
exports.snakeCase = snakeCase;
|
|
9
|
+
const wrap = (value) => Array.isArray(value)
|
|
10
|
+
? value
|
|
11
|
+
: [value];
|
|
12
|
+
exports.wrap = wrap;
|
|
13
|
+
function raise(ctorOrMessage, messageOrCause, cause) {
|
|
14
|
+
if (typeof ctorOrMessage === 'string')
|
|
15
|
+
throw new Error(ctorOrMessage, { cause: messageOrCause });
|
|
16
|
+
throw new ctorOrMessage(messageOrCause, { cause });
|
|
17
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { SQL } from 'bun';
|
|
2
|
+
const TYPES = {
|
|
3
|
+
BIGINT: () => 'BIGINT',
|
|
4
|
+
BINARY: () => 'BYTEA',
|
|
5
|
+
BOOLEAN: () => 'BOOLEAN',
|
|
6
|
+
CITEXT: () => 'CITEXT',
|
|
7
|
+
DATE: () => 'DATE',
|
|
8
|
+
DECIMAL: ({ precision, scale }) => precision !== undefined && scale !== undefined ? `NUMERIC(${precision}, ${scale})` : 'NUMERIC',
|
|
9
|
+
FLOAT: () => 'DOUBLE PRECISION',
|
|
10
|
+
INTEGER: () => 'BIGINT',
|
|
11
|
+
JSON: () => 'JSONB',
|
|
12
|
+
SEQUENCE: () => 'BIGINT',
|
|
13
|
+
STRING: ({ length = 255 }) => `VARCHAR(${length})`,
|
|
14
|
+
TEXT: () => 'TEXT',
|
|
15
|
+
TIMESTAMP: () => 'TIMESTAMPTZ',
|
|
16
|
+
UUID: () => 'UUID',
|
|
17
|
+
};
|
|
18
|
+
const identity = () => ({
|
|
19
|
+
encode: (value) => value,
|
|
20
|
+
decode: (value) => value,
|
|
21
|
+
});
|
|
22
|
+
// Bun.SQL reads bigints (BIGINT columns, COUNT and SUM) and NUMERICs as
|
|
23
|
+
// strings
|
|
24
|
+
const numeric = {
|
|
25
|
+
encode: (value) => value,
|
|
26
|
+
decode: (value) => Number(value),
|
|
27
|
+
};
|
|
28
|
+
// `#>>` reads text, so fields of other types are cast back to theirs
|
|
29
|
+
const CASTS = {
|
|
30
|
+
BIGINT: 'BIGINT',
|
|
31
|
+
BOOLEAN: 'BOOLEAN',
|
|
32
|
+
DECIMAL: 'NUMERIC',
|
|
33
|
+
FLOAT: 'DOUBLE PRECISION',
|
|
34
|
+
INTEGER: 'DOUBLE PRECISION',
|
|
35
|
+
SEQUENCE: 'BIGINT',
|
|
36
|
+
};
|
|
37
|
+
const jsonPath = (path) => `'{${path.map(key => `"${key.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('\'', '\'\'')}"`).join(',')}}'`;
|
|
38
|
+
const json = (sql, path, type) => {
|
|
39
|
+
if (type === 'JSON')
|
|
40
|
+
return `${sql} #> ${jsonPath(path)}`;
|
|
41
|
+
const text = `${sql} #>> ${jsonPath(path)}`;
|
|
42
|
+
return CASTS[type] ? `(${text})::${CASTS[type]}` : text;
|
|
43
|
+
};
|
|
44
|
+
// Bun.SQL sends objects and arrays as JSON, and reads JSONB already parsed
|
|
45
|
+
const dialect = {
|
|
46
|
+
name: 'postgres',
|
|
47
|
+
placeholder: (index) => `$${index}`,
|
|
48
|
+
type: (col) => TYPES[col.type](col),
|
|
49
|
+
generated: (col) => col.type === 'SEQUENCE'
|
|
50
|
+
? 'GENERATED ALWAYS AS IDENTITY'
|
|
51
|
+
: undefined,
|
|
52
|
+
json,
|
|
53
|
+
codecs: {
|
|
54
|
+
BIGINT: { encode: (value) => value, decode: (value) => BigInt(value) },
|
|
55
|
+
BINARY: { encode: (value) => value, decode: (value) => new Uint8Array(value) },
|
|
56
|
+
BOOLEAN: identity(),
|
|
57
|
+
CITEXT: identity(),
|
|
58
|
+
DATE: { encode: (value) => value, decode: (value) => value instanceof Date ? value.toISOString().slice(0, 10) : value },
|
|
59
|
+
DECIMAL: numeric,
|
|
60
|
+
FLOAT: numeric,
|
|
61
|
+
INTEGER: numeric,
|
|
62
|
+
JSON: identity(),
|
|
63
|
+
SEQUENCE: numeric,
|
|
64
|
+
STRING: identity(),
|
|
65
|
+
TEXT: identity(),
|
|
66
|
+
TIMESTAMP: { encode: (value) => value, decode: (value) => new Date(value) },
|
|
67
|
+
UUID: identity(),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
class BunPostgres {
|
|
71
|
+
sql;
|
|
72
|
+
loggers;
|
|
73
|
+
inTransaction;
|
|
74
|
+
dialect = dialect;
|
|
75
|
+
constructor(sql, loggers = [], inTransaction = false) {
|
|
76
|
+
this.sql = sql;
|
|
77
|
+
this.loggers = loggers;
|
|
78
|
+
this.inTransaction = inTransaction;
|
|
79
|
+
}
|
|
80
|
+
async execute(sql, params) {
|
|
81
|
+
return await this.sql.unsafe(sql, params);
|
|
82
|
+
}
|
|
83
|
+
async close() {
|
|
84
|
+
await this.sql.close();
|
|
85
|
+
}
|
|
86
|
+
// nested transactions are savepoints of the outer one
|
|
87
|
+
async transaction(fn) {
|
|
88
|
+
const callback = (tx) => fn(new BunPostgres(tx, this.loggers, true));
|
|
89
|
+
const result = this.inTransaction
|
|
90
|
+
? this.sql.savepoint(callback)
|
|
91
|
+
: this.sql.begin(callback);
|
|
92
|
+
return await result;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const connect = (url, { loggers = [] } = {}) => new BunPostgres(new SQL(url), loggers);
|
|
96
|
+
export { connect, dialect, };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
const TYPES = {
|
|
3
|
+
BIGINT: 'INTEGER',
|
|
4
|
+
BINARY: 'BLOB',
|
|
5
|
+
BOOLEAN: 'INTEGER',
|
|
6
|
+
CITEXT: 'TEXT COLLATE NOCASE',
|
|
7
|
+
DATE: 'TEXT',
|
|
8
|
+
DECIMAL: 'NUMERIC',
|
|
9
|
+
FLOAT: 'REAL',
|
|
10
|
+
INTEGER: 'INTEGER',
|
|
11
|
+
JSON: 'TEXT',
|
|
12
|
+
SEQUENCE: 'INTEGER',
|
|
13
|
+
STRING: 'TEXT',
|
|
14
|
+
TEXT: 'TEXT',
|
|
15
|
+
TIMESTAMP: 'INTEGER',
|
|
16
|
+
UUID: 'TEXT',
|
|
17
|
+
};
|
|
18
|
+
const identity = () => ({
|
|
19
|
+
encode: (value) => value,
|
|
20
|
+
decode: (value) => value,
|
|
21
|
+
});
|
|
22
|
+
// integers are read as bigints (see `execute`), so those that aren't BIGINT
|
|
23
|
+
// columns go back to being numbers
|
|
24
|
+
const numeric = {
|
|
25
|
+
encode: (value) => value,
|
|
26
|
+
decode: (value) => Number(value),
|
|
27
|
+
};
|
|
28
|
+
// sqlite's JSON paths quote each key, so that any key is a valid one
|
|
29
|
+
const jsonPath = (path) => `'$${path.map(key => `."${key.replaceAll('"', '\\"').replaceAll('\'', '\'\'')}"`).join('')}'`;
|
|
30
|
+
const dialect = {
|
|
31
|
+
name: 'sqlite',
|
|
32
|
+
placeholder: () => '?',
|
|
33
|
+
type: (col) => TYPES[col.type],
|
|
34
|
+
generated: (col) => {
|
|
35
|
+
if (col.type === 'SEQUENCE' && col.primaryKey)
|
|
36
|
+
return 'AUTOINCREMENT';
|
|
37
|
+
if (col.type === 'SEQUENCE')
|
|
38
|
+
throw new Error(`SQLite can only generate sequences for primary keys, column "${col.name}" isn't one`);
|
|
39
|
+
return undefined;
|
|
40
|
+
},
|
|
41
|
+
json: (sql, path, type) => `${sql} ${type === 'JSON' ? '->' : '->>'} ${jsonPath(path)}`,
|
|
42
|
+
codecs: {
|
|
43
|
+
BIGINT: { encode: (value) => value, decode: (value) => BigInt(value) },
|
|
44
|
+
BINARY: { encode: (value) => value, decode: (value) => new Uint8Array(value) },
|
|
45
|
+
BOOLEAN: { encode: (value) => (value ? 1 : 0), decode: (value) => Number(value) === 1 },
|
|
46
|
+
CITEXT: identity(),
|
|
47
|
+
DATE: identity(),
|
|
48
|
+
DECIMAL: numeric,
|
|
49
|
+
FLOAT: numeric,
|
|
50
|
+
INTEGER: numeric,
|
|
51
|
+
JSON: { encode: (value) => JSON.stringify(value), decode: (value) => JSON.parse(value) },
|
|
52
|
+
SEQUENCE: numeric,
|
|
53
|
+
STRING: identity(),
|
|
54
|
+
TEXT: identity(),
|
|
55
|
+
TIMESTAMP: { encode: (value) => value.getTime(), decode: (value) => new Date(Number(value)) },
|
|
56
|
+
UUID: identity(),
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
class BunSQLite {
|
|
60
|
+
conn;
|
|
61
|
+
loggers;
|
|
62
|
+
depth;
|
|
63
|
+
dialect = dialect;
|
|
64
|
+
constructor(conn, loggers = [], depth = 0) {
|
|
65
|
+
this.conn = conn;
|
|
66
|
+
this.loggers = loggers;
|
|
67
|
+
this.depth = depth;
|
|
68
|
+
}
|
|
69
|
+
async execute(sql, params) {
|
|
70
|
+
// without safe integers, bun:sqlite rounds anything beyond 2^53
|
|
71
|
+
return this.conn.prepare(sql).safeIntegers(true).all(...params);
|
|
72
|
+
}
|
|
73
|
+
async close() {
|
|
74
|
+
this.conn.close();
|
|
75
|
+
}
|
|
76
|
+
// bun:sqlite's own `transaction()` is synchronous and would commit before an
|
|
77
|
+
// async callback settles, hence the manual BEGIN/COMMIT (SAVEPOINT if nested)
|
|
78
|
+
async transaction(fn) {
|
|
79
|
+
const savepoint = `"sp_${this.depth}"`;
|
|
80
|
+
const [begin, commit, rollback] = this.depth === 0
|
|
81
|
+
? ['BEGIN', 'COMMIT', 'ROLLBACK']
|
|
82
|
+
: [`SAVEPOINT ${savepoint}`, `RELEASE ${savepoint}`, `ROLLBACK TO ${savepoint}; RELEASE ${savepoint}`];
|
|
83
|
+
this.conn.run(begin);
|
|
84
|
+
try {
|
|
85
|
+
const result = await fn(new BunSQLite(this.conn, this.loggers, this.depth + 1));
|
|
86
|
+
this.conn.run(commit);
|
|
87
|
+
return result;
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
this.conn.run(rollback);
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
const connect = (filename = ':memory:', { loggers = [] } = {}) => new BunSQLite(new Database(filename), loggers);
|
|
96
|
+
export { connect, dialect, };
|