@uniflowed/sql 0.14.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/sqlite.js ADDED
@@ -0,0 +1,178 @@
1
+ // @flow
2
+ //
3
+ // `@uniflowed/sql/sqlite`: SQLite's storage classes, both ways.
4
+ //
5
+ // SQLite has five: NULL, INTEGER, REAL, TEXT and BLOB. Every SQLite adapter
6
+ // turns safe integers on, so an INTEGER arrives as a `bigint` (or, from a
7
+ // driver that cannot, a `number`) and nothing past 2^53 is rounded before a
8
+ // codec sees it. A declared column type is only an affinity in SQLite, so a
9
+ // codec accepts the storage classes that affinity can produce and refuses the
10
+ // rest — a `TEXT` in an `INTEGER` column is an error here, not a string.
11
+
12
+ import type { JsonValue, SqlParam } from "./index.js";
13
+ import { decodeError, encodeError } from "./index.js";
14
+ import {
15
+ bigintFrom,
16
+ bigintParam,
17
+ bytesFrom,
18
+ bytesParam,
19
+ enumName,
20
+ integerParam,
21
+ jsonFromText,
22
+ jsonParam,
23
+ label,
24
+ labelParam,
25
+ numberParam,
26
+ safeInteger,
27
+ stringFrom,
28
+ stringParam,
29
+ } from "./internal/shared.js";
30
+
31
+ /** How one declared type becomes a Flow value and back. */
32
+ export type Codec<T> = {|
33
+ readonly sqlType: string,
34
+ readonly decode: (value: mixed) => T,
35
+ readonly encode: (value: T) => SqlParam,
36
+ |};
37
+
38
+ /** `INTEGER` as a `number`, which throws past 2^53 instead of rounding. */
39
+ export const integer: Codec<number> = {
40
+ sqlType: "INTEGER",
41
+ decode: (value) => {
42
+ if (typeof value === "number" || typeof value === "bigint") {
43
+ return safeInteger("INTEGER", value);
44
+ }
45
+ return decodeError("INTEGER", value);
46
+ },
47
+ encode: (value) => integerParam("INTEGER (an integer within ±2^53)", value),
48
+ };
49
+
50
+ /** `INTEGER` as a `bigint`, for `sqliteInteger: "bigint"`. */
51
+ export const integerAsBigint: Codec<bigint> = {
52
+ sqlType: "INTEGER",
53
+ decode: (value) => bigintFrom("INTEGER", value),
54
+ encode: (value) => bigintParam("INTEGER (a bigint)", value),
55
+ };
56
+
57
+ /** `REAL`, `DOUBLE`, `FLOAT`, and `NUMERIC`/`DECIMAL`, which SQLite stores as one or an INTEGER. */
58
+ export function real(sqlType: string): Codec<number> {
59
+ return {
60
+ sqlType,
61
+ decode: (value) => {
62
+ if (typeof value === "number") {
63
+ return value;
64
+ }
65
+ if (typeof value === "bigint") {
66
+ return safeInteger(sqlType, value);
67
+ }
68
+ return decodeError(sqlType, value);
69
+ },
70
+ encode: (value) => numberParam(`${sqlType} (a number)`, value),
71
+ };
72
+ }
73
+
74
+ /** `BOOLEAN`: SQLite stores 0 and 1. */
75
+ export const boolean: Codec<boolean> = {
76
+ sqlType: "BOOLEAN",
77
+ decode: (value) => {
78
+ if (value === 0 || value === 0n) {
79
+ return false;
80
+ }
81
+ if (value === 1 || value === 1n) {
82
+ return true;
83
+ }
84
+ return decodeError("BOOLEAN (0 or 1)", value);
85
+ },
86
+ encode: (value) => {
87
+ if (typeof value !== "boolean") {
88
+ return encodeError("BOOLEAN (a boolean)", value);
89
+ }
90
+ return value ? 1 : 0;
91
+ },
92
+ };
93
+
94
+ /** `TEXT` and the types SQLite stores as text: `VARCHAR`, `DATE`, `DATETIME`, `TIMESTAMP`. */
95
+ export function text(sqlType: string): Codec<string> {
96
+ return {
97
+ sqlType,
98
+ decode: (value) => stringFrom(sqlType, value),
99
+ encode: (value) => stringParam(`${sqlType} (a string)`, value),
100
+ };
101
+ }
102
+
103
+ /** `TEXT`. */
104
+ export const string: Codec<string> = text("TEXT");
105
+
106
+ /** `BLOB`. */
107
+ export const blob: Codec<Uint8Array> = {
108
+ sqlType: "BLOB",
109
+ decode: (value) => bytesFrom("BLOB", value),
110
+ encode: (value) => bytesParam("BLOB (a Uint8Array)", value),
111
+ };
112
+
113
+ /** `JSON` and `JSONB` (sqlc reads the latter through `json()`), stored as text. */
114
+ export const json: Codec<JsonValue> = {
115
+ sqlType: "JSON",
116
+ decode: (value) => jsonFromText(value),
117
+ encode: (value) => jsonParam(value),
118
+ };
119
+
120
+ /** A declared type with no affinity uf can type, or a result sqlc could not. */
121
+ export const unknown: Codec<mixed> = {
122
+ sqlType: "unknown",
123
+ decode: (value) => value,
124
+ encode: (value) => {
125
+ if (
126
+ value === null ||
127
+ typeof value === "string" ||
128
+ typeof value === "number" ||
129
+ typeof value === "bigint" ||
130
+ value instanceof Uint8Array
131
+ ) {
132
+ return value;
133
+ }
134
+ if (typeof value === "boolean") {
135
+ return value ? 1 : 0;
136
+ }
137
+ return jsonParam(value);
138
+ },
139
+ };
140
+
141
+ /** A `CHECK (x IN (…))`-style enum sqlc was told about through an override. */
142
+ export function enumeration<T extends string>(
143
+ sqlType: string,
144
+ values: $ReadOnlyArray<T>,
145
+ ): Codec<T> {
146
+ const expected = `${sqlType} (${enumName(values)})`;
147
+ return {
148
+ sqlType,
149
+ decode: (value) => label(values, expected, value),
150
+ encode: (value) => labelParam(values, expected, value),
151
+ };
152
+ }
153
+
154
+ /**
155
+ * An override's codec: `decode` narrows what `codec` read, `encode` widens a
156
+ * value back to what `codec` writes.
157
+ */
158
+ export function map<T, U>(
159
+ codec: Codec<T>,
160
+ decode: (value: T) => U,
161
+ encode: (value: U) => T,
162
+ ): Codec<U> {
163
+ return {
164
+ sqlType: codec.sqlType,
165
+ decode: (value) => decode(codec.decode(value)),
166
+ encode: (value) => codec.encode(encode(value)),
167
+ };
168
+ }
169
+
170
+ /** Decode a nullable column. */
171
+ export function nullable<T>(codec: Codec<T>, value: mixed): T | null {
172
+ return value === null ? null : codec.decode(value);
173
+ }
174
+
175
+ /** Encode a nullable parameter. */
176
+ export function param<T>(codec: Codec<T>, value: T | null): SqlParam {
177
+ return value === null ? null : codec.encode(value);
178
+ }