@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,237 @@
|
|
|
1
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
2
|
+
// STANDARD SCHEMA SPEC //
|
|
3
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
4
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
5
|
+
// STANDARD SCHEMA API //
|
|
6
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
7
|
+
/**
|
|
8
|
+
* @public The Standard Schema interface.
|
|
9
|
+
* @since 0.1.0
|
|
10
|
+
* @version 1
|
|
11
|
+
*/
|
|
12
|
+
export {};
|
|
13
|
+
/**
|
|
14
|
+
* @public Thrown when a value fails validation, carrying every issue found.
|
|
15
|
+
* @since 0.1.0
|
|
16
|
+
* @version 1
|
|
17
|
+
*/
|
|
18
|
+
export class ValidationError extends Error {
|
|
19
|
+
issues;
|
|
20
|
+
constructor(issues) {
|
|
21
|
+
super('Failed validation:\n' + issues.map(issue => ` - ${formatPath(issue.path)}${issue.message}`).join('\n'));
|
|
22
|
+
this.issues = issues;
|
|
23
|
+
this.name = 'ValidationError';
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* @public Use any standard schema to parse a value.
|
|
28
|
+
* @since 0.1.0
|
|
29
|
+
* @version 1
|
|
30
|
+
*/
|
|
31
|
+
export const parse = (schema, value) => {
|
|
32
|
+
const parsed = schema['~standard'].validate(value);
|
|
33
|
+
if (parsed instanceof Promise) {
|
|
34
|
+
throw new Error('async standard schema validators are not supported');
|
|
35
|
+
}
|
|
36
|
+
return parsed;
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* @public Like {@link parse}, but returns the value or throws a
|
|
40
|
+
* {@link ValidationError}.
|
|
41
|
+
* @since 0.1.0
|
|
42
|
+
* @version 1
|
|
43
|
+
*/
|
|
44
|
+
export const parseOrThrow = (schema, value) => {
|
|
45
|
+
const parsed = parse(schema, value);
|
|
46
|
+
if (parsed.issues)
|
|
47
|
+
throw new ValidationError(parsed.issues);
|
|
48
|
+
return parsed.value;
|
|
49
|
+
};
|
|
50
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
51
|
+
// UTILS //
|
|
52
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
53
|
+
const formatPath = (path) => path && path.length > 0
|
|
54
|
+
? path.map(segment => String(typeof segment === 'object' ? segment.key : segment)).join('.') + ': '
|
|
55
|
+
: '';
|
|
56
|
+
const prependPath = (path) => (issue) => ({
|
|
57
|
+
...issue,
|
|
58
|
+
path: [path, ...(issue.path ?? [])],
|
|
59
|
+
});
|
|
60
|
+
const define = (validate) => ({
|
|
61
|
+
'~standard': { version: 1, vendor: 'exto', validate },
|
|
62
|
+
});
|
|
63
|
+
const fail = (message) => ({ issues: [{ message }] });
|
|
64
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
65
|
+
// BUILT-IN VALIDATORS //
|
|
66
|
+
// // // // // // // // // // // // // // // // // // // // // // // //
|
|
67
|
+
/**
|
|
68
|
+
* @public Defines an array of a given standard schema.
|
|
69
|
+
* @since 0.1.0
|
|
70
|
+
* @version 1
|
|
71
|
+
*/
|
|
72
|
+
export const array = (schema) => define((input) => {
|
|
73
|
+
if (!Array.isArray(input))
|
|
74
|
+
return fail('must be an array');
|
|
75
|
+
const issues = [];
|
|
76
|
+
const value = [];
|
|
77
|
+
input.forEach((item, i) => {
|
|
78
|
+
const parsed = parse(schema, item);
|
|
79
|
+
if (parsed.issues)
|
|
80
|
+
issues.push(...parsed.issues.map(prependPath(i)));
|
|
81
|
+
else
|
|
82
|
+
value.push(parsed.value);
|
|
83
|
+
});
|
|
84
|
+
return issues.length > 0
|
|
85
|
+
? { issues }
|
|
86
|
+
: { value };
|
|
87
|
+
});
|
|
88
|
+
/**
|
|
89
|
+
* @public Defines a standard schema for bigints.
|
|
90
|
+
* @since 0.1.0
|
|
91
|
+
* @version 1
|
|
92
|
+
*/
|
|
93
|
+
export const bigint = () => define((input) => typeof input === 'bigint'
|
|
94
|
+
? { value: input }
|
|
95
|
+
: fail('must be a bigint'));
|
|
96
|
+
/**
|
|
97
|
+
* @public Defines a standard schema for boolean values.
|
|
98
|
+
* @since 0.1.0
|
|
99
|
+
* @version 1
|
|
100
|
+
*/
|
|
101
|
+
export const boolean = () => define((input) => typeof input === 'boolean'
|
|
102
|
+
? { value: input }
|
|
103
|
+
: fail('must be a 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
|
+
export 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
|
+
/**
|
|
119
|
+
* @public Defines a standard schema for one of the given string values.
|
|
120
|
+
* @since 0.1.0
|
|
121
|
+
* @version 1
|
|
122
|
+
*/
|
|
123
|
+
export const enumOf = (values) => define((input) => values.includes(input)
|
|
124
|
+
? { value: input }
|
|
125
|
+
: fail(`must be one of: ${values.join(', ')}`));
|
|
126
|
+
/**
|
|
127
|
+
* @public Defines a standard schema for instances of a given class.
|
|
128
|
+
* @since 0.1.0
|
|
129
|
+
* @version 1
|
|
130
|
+
*/
|
|
131
|
+
export const instanceOf = (ctor) => define((input) => input instanceof ctor
|
|
132
|
+
? { value: input }
|
|
133
|
+
: fail(`must be an instance of ${ctor.name}`));
|
|
134
|
+
/**
|
|
135
|
+
* @public Defines a standard schema for integers, within the safe integer
|
|
136
|
+
* range by default.
|
|
137
|
+
* @since 0.1.0
|
|
138
|
+
* @version 1
|
|
139
|
+
*/
|
|
140
|
+
export const integer = ({ min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = {}) => define((input) => {
|
|
141
|
+
if (!Number.isInteger(input))
|
|
142
|
+
return fail('must be an integer');
|
|
143
|
+
if (input < min)
|
|
144
|
+
return fail(`must be greater than or equal to ${min}`);
|
|
145
|
+
if (input > max)
|
|
146
|
+
return fail(`must be less than or equal to ${max}`);
|
|
147
|
+
return { value: input };
|
|
148
|
+
});
|
|
149
|
+
/**
|
|
150
|
+
* @public Defines a standard schema for calendar dates as `YYYY-MM-DD`
|
|
151
|
+
* strings.
|
|
152
|
+
* @since 0.1.0
|
|
153
|
+
* @version 1
|
|
154
|
+
*/
|
|
155
|
+
export const isoDate = () => define((input) => {
|
|
156
|
+
const valid = typeof input === 'string'
|
|
157
|
+
&& /^\d{4}-\d{2}-\d{2}$/.test(input)
|
|
158
|
+
&& new Date(`${input}T00:00:00Z`).toISOString().startsWith(input);
|
|
159
|
+
return valid
|
|
160
|
+
? { value: input }
|
|
161
|
+
: fail('must be a valid date in the YYYY-MM-DD format');
|
|
162
|
+
});
|
|
163
|
+
/**
|
|
164
|
+
* @public Makes a given standard schema accept `null`.
|
|
165
|
+
* @since 0.1.0
|
|
166
|
+
* @version 1
|
|
167
|
+
*/
|
|
168
|
+
export const nullable = (schema) => define((input) => input === null
|
|
169
|
+
? { value: null }
|
|
170
|
+
: parse(schema, input));
|
|
171
|
+
/**
|
|
172
|
+
* @public Defines a standard schema for finite numbers.
|
|
173
|
+
* @since 0.1.0
|
|
174
|
+
* @version 1
|
|
175
|
+
*/
|
|
176
|
+
export const number = () => define((input) => Number.isFinite(input)
|
|
177
|
+
? { value: input }
|
|
178
|
+
: fail('must be a finite number'));
|
|
179
|
+
/**
|
|
180
|
+
* @public Makes a given standard schema accept `undefined`.
|
|
181
|
+
* @since 0.1.0
|
|
182
|
+
* @version 1
|
|
183
|
+
*/
|
|
184
|
+
export const optional = (schema) => define((input) => input === undefined
|
|
185
|
+
? { value: undefined }
|
|
186
|
+
: parse(schema, input));
|
|
187
|
+
/**
|
|
188
|
+
* @public Defines a standard schema for plain objects with string keys.
|
|
189
|
+
* @since 0.2.0
|
|
190
|
+
* @version 1
|
|
191
|
+
*/
|
|
192
|
+
export const record = () => define((input) => typeof input === 'object' && input !== null && Object.getPrototypeOf(input) === Object.prototype
|
|
193
|
+
? { value: input }
|
|
194
|
+
: fail('must be a plain object'));
|
|
195
|
+
/**
|
|
196
|
+
* @public Defines a standard schema for strings, optionally limited in
|
|
197
|
+
* length.
|
|
198
|
+
* @since 0.1.0
|
|
199
|
+
* @version 1
|
|
200
|
+
*/
|
|
201
|
+
export const string = ({ max } = {}) => define((input) => {
|
|
202
|
+
if (typeof input !== 'string')
|
|
203
|
+
return fail('must be a string');
|
|
204
|
+
if (max !== undefined && input.length > max)
|
|
205
|
+
return fail(`must be at most ${max} characters long`);
|
|
206
|
+
return { value: input };
|
|
207
|
+
});
|
|
208
|
+
export const strictObject = (shape) => define((input) => {
|
|
209
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input))
|
|
210
|
+
return fail('must be an object');
|
|
211
|
+
const record = input;
|
|
212
|
+
const issues = Object
|
|
213
|
+
.keys(record)
|
|
214
|
+
.filter(key => !(key in shape))
|
|
215
|
+
.map(key => ({ path: [key], message: 'unknown field' }));
|
|
216
|
+
const value = {};
|
|
217
|
+
for (const [key, schema] of Object.entries(shape)) {
|
|
218
|
+
const parsed = parse(schema, record[key]);
|
|
219
|
+
if (parsed.issues && record[key] === undefined)
|
|
220
|
+
issues.push({ path: [key], message: 'is required' });
|
|
221
|
+
else if (parsed.issues)
|
|
222
|
+
issues.push(...parsed.issues.map(prependPath(key)));
|
|
223
|
+
else if (parsed.value !== undefined)
|
|
224
|
+
value[key] = parsed.value;
|
|
225
|
+
}
|
|
226
|
+
return issues.length > 0
|
|
227
|
+
? { issues }
|
|
228
|
+
: { value: value };
|
|
229
|
+
});
|
|
230
|
+
/**
|
|
231
|
+
* @public Defines a standard schema for UUID strings (any version).
|
|
232
|
+
* @since 0.1.0
|
|
233
|
+
* @version 1
|
|
234
|
+
*/
|
|
235
|
+
export 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)
|
|
236
|
+
? { value: input }
|
|
237
|
+
: fail('must be a valid UUID'));
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export const camelCase = (str) => str.replace(/_([a-z0-9])/g, (_, char) => char.toUpperCase());
|
|
2
|
+
export const snakeCase = (str) => str.replace(/[A-Z]/g, (char) => `_${char.toLowerCase()}`);
|
|
3
|
+
export const wrap = (value) => Array.isArray(value)
|
|
4
|
+
? value
|
|
5
|
+
: [value];
|
|
6
|
+
export function raise(ctorOrMessage, messageOrCause, cause) {
|
|
7
|
+
if (typeof ctorOrMessage === 'string')
|
|
8
|
+
throw new Error(ctorOrMessage, { cause: messageOrCause });
|
|
9
|
+
throw new ctorOrMessage(messageOrCause, { cause });
|
|
10
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { SQL } from 'bun';
|
|
2
|
+
import type { Dialect, IDatabase, ILogger, Row } from './index';
|
|
3
|
+
declare const dialect: Dialect;
|
|
4
|
+
declare class BunPostgres implements IDatabase {
|
|
5
|
+
private readonly sql;
|
|
6
|
+
readonly loggers: ILogger[];
|
|
7
|
+
private readonly inTransaction;
|
|
8
|
+
readonly dialect: Dialect;
|
|
9
|
+
constructor(sql: SQL, loggers?: ILogger[], inTransaction?: boolean);
|
|
10
|
+
execute(sql: string, params: unknown[]): Promise<Row[] & SQL.ResultMetadata>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
transaction<T>(fn: (tx: IDatabase) => Promise<T>): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
declare const connect: (url: string, { loggers }?: {
|
|
15
|
+
loggers?: ILogger[];
|
|
16
|
+
}) => BunPostgres;
|
|
17
|
+
export { connect, dialect, };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import type { Dialect, IDatabase, ILogger, Row } from './index';
|
|
3
|
+
declare const dialect: Dialect;
|
|
4
|
+
declare class BunSQLite implements IDatabase {
|
|
5
|
+
private readonly conn;
|
|
6
|
+
readonly loggers: ILogger[];
|
|
7
|
+
private readonly depth;
|
|
8
|
+
readonly dialect: Dialect;
|
|
9
|
+
constructor(conn: Database, loggers?: ILogger[], depth?: number);
|
|
10
|
+
execute(sql: string, params: unknown[]): Promise<Row[]>;
|
|
11
|
+
close(): Promise<void>;
|
|
12
|
+
transaction<T>(fn: (tx: IDatabase) => Promise<T>): Promise<T>;
|
|
13
|
+
}
|
|
14
|
+
declare const connect: (filename?: string, { loggers }?: {
|
|
15
|
+
loggers?: ILogger[];
|
|
16
|
+
}) => BunSQLite;
|
|
17
|
+
export { connect, dialect, };
|