@leonardovida-md/drizzle-neo-duckdb 1.0.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.
@@ -0,0 +1,303 @@
1
+ import {
2
+ Column,
3
+ SQL,
4
+ getTableName,
5
+ is,
6
+ type AnyColumn,
7
+ type DriverValueDecoder,
8
+ type SelectedFieldsOrdered,
9
+ } from 'drizzle-orm';
10
+ import {
11
+ PgCustomColumn,
12
+ PgDate,
13
+ PgDateString,
14
+ PgInterval,
15
+ PgTime,
16
+ PgTimestamp,
17
+ PgTimestampString,
18
+ } from 'drizzle-orm/pg-core';
19
+
20
+ type SQLInternal<T = unknown> = SQL<T> & {
21
+ decoder: DriverValueDecoder<T, any>;
22
+ };
23
+
24
+ type DecoderInput<TDecoder extends DriverValueDecoder<unknown, unknown>> =
25
+ Parameters<TDecoder['mapFromDriverValue']>[0];
26
+
27
+ function toDecoderInput<TDecoder extends DriverValueDecoder<unknown, unknown>>(
28
+ decoder: TDecoder,
29
+ value: unknown
30
+ ): DecoderInput<TDecoder> {
31
+ void decoder;
32
+ return value as DecoderInput<TDecoder>;
33
+ }
34
+
35
+ function normalizeInet(value: unknown): unknown {
36
+ if (
37
+ value &&
38
+ typeof value === 'object' &&
39
+ 'address' in value &&
40
+ typeof (value as { address: unknown }).address !== 'undefined'
41
+ ) {
42
+ const { address, mask } = value as {
43
+ address: bigint | number;
44
+ mask?: number;
45
+ };
46
+
47
+ if (typeof address === 'bigint' || typeof address === 'number') {
48
+ const inet = typeof address === 'number' ? BigInt(address) : address;
49
+ const maxIpv4 = (1n << 32n) - 1n;
50
+ if (inet >= 0 && inet <= maxIpv4) {
51
+ const num = Number(inet);
52
+ const octets = [
53
+ (num >>> 24) & 255,
54
+ (num >>> 16) & 255,
55
+ (num >>> 8) & 255,
56
+ num & 255,
57
+ ];
58
+ const suffix =
59
+ typeof mask === 'number' && mask !== 32 ? `/${mask}` : '';
60
+ return `${octets.join('.')}${suffix}`;
61
+ }
62
+ }
63
+
64
+ const fallback = (value as { toString?: () => string }).toString?.();
65
+ if (fallback && fallback !== '[object Object]') {
66
+ return fallback;
67
+ }
68
+ }
69
+
70
+ return value;
71
+ }
72
+
73
+ function normalizeTimestampString(
74
+ value: unknown,
75
+ withTimezone: boolean
76
+ ): string | unknown {
77
+ if (value instanceof Date) {
78
+ const iso = value.toISOString().replace('T', ' ');
79
+ return withTimezone ? iso.replace('Z', '+00') : iso.replace('Z', '');
80
+ }
81
+ if (typeof value === 'string') {
82
+ const normalized = value.replace('T', ' ');
83
+ if (withTimezone) {
84
+ return normalized.includes('+') ? normalized : `${normalized}+00`;
85
+ }
86
+ return normalized.replace(/\+00$/, '');
87
+ }
88
+ return value;
89
+ }
90
+
91
+ function normalizeTimestamp(value: unknown, withTimezone: boolean): Date | unknown {
92
+ if (value instanceof Date) {
93
+ return value;
94
+ }
95
+ if (typeof value === 'string') {
96
+ const hasOffset =
97
+ value.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(value.trim());
98
+ const spaced = value.replace(' ', 'T');
99
+ const normalized =
100
+ withTimezone || hasOffset ? spaced : `${spaced}+00`;
101
+ return new Date(normalized);
102
+ }
103
+ return value;
104
+ }
105
+
106
+ function normalizeDateString(value: unknown): string | unknown {
107
+ if (value instanceof Date) {
108
+ return value.toISOString().slice(0, 10);
109
+ }
110
+ if (typeof value === 'string') {
111
+ return value.slice(0, 10);
112
+ }
113
+ return value;
114
+ }
115
+
116
+ function normalizeDateValue(value: unknown): Date | unknown {
117
+ if (value instanceof Date) {
118
+ return value;
119
+ }
120
+ if (typeof value === 'string') {
121
+ return new Date(`${value.slice(0, 10)}T00:00:00Z`);
122
+ }
123
+ return value;
124
+ }
125
+
126
+ function normalizeTime(value: unknown): string | unknown {
127
+ if (typeof value === 'bigint') {
128
+ const totalMillis = Number(value) / 1000;
129
+ const date = new Date(totalMillis);
130
+ return date.toISOString().split('T')[1]!.replace('Z', '');
131
+ }
132
+ if (value instanceof Date) {
133
+ return value.toISOString().split('T')[1]!.replace('Z', '');
134
+ }
135
+ return value;
136
+ }
137
+
138
+ function normalizeInterval(value: unknown): string | unknown {
139
+ if (
140
+ value &&
141
+ typeof value === 'object' &&
142
+ 'days' in value &&
143
+ 'months' in value
144
+ ) {
145
+ const { months, days, micros } = value as {
146
+ months: number;
147
+ days: number;
148
+ micros?: number | string;
149
+ };
150
+
151
+ if (months === 0 && days !== undefined) {
152
+ if (micros && Number(micros) !== 0) {
153
+ const seconds = Number(micros) / 1_000_000;
154
+ return `${days} day${days === 1 ? '' : 's'} ${seconds} seconds`.trim();
155
+ }
156
+ return `${days} day${days === 1 ? '' : 's'}`;
157
+ }
158
+ }
159
+ return value;
160
+ }
161
+
162
+ function mapDriverValue(
163
+ decoder: DriverValueDecoder<unknown, unknown>,
164
+ rawValue: unknown
165
+ ): unknown {
166
+ if (is(decoder, PgTimestampString)) {
167
+ return decoder.mapFromDriverValue(
168
+ toDecoderInput(
169
+ decoder,
170
+ normalizeTimestampString(rawValue, decoder.withTimezone)
171
+ )
172
+ );
173
+ }
174
+
175
+ if (is(decoder, PgTimestamp)) {
176
+ const normalized = normalizeTimestamp(rawValue, decoder.withTimezone);
177
+ if (normalized instanceof Date) {
178
+ return normalized;
179
+ }
180
+ return decoder.mapFromDriverValue(
181
+ toDecoderInput(decoder, normalized)
182
+ );
183
+ }
184
+
185
+ if (is(decoder, PgDateString)) {
186
+ return decoder.mapFromDriverValue(
187
+ toDecoderInput(decoder, normalizeDateString(rawValue))
188
+ );
189
+ }
190
+
191
+ if (is(decoder, PgDate)) {
192
+ return decoder.mapFromDriverValue(
193
+ toDecoderInput(decoder, normalizeDateValue(rawValue))
194
+ );
195
+ }
196
+
197
+ if (is(decoder, PgTime)) {
198
+ return decoder.mapFromDriverValue(
199
+ toDecoderInput(decoder, normalizeTime(rawValue))
200
+ );
201
+ }
202
+
203
+ if (is(decoder, PgInterval)) {
204
+ return decoder.mapFromDriverValue(
205
+ toDecoderInput(decoder, normalizeInterval(rawValue))
206
+ );
207
+ }
208
+
209
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, rawValue));
210
+ }
211
+
212
+ export function mapResultRow<TResult>(
213
+ columns: SelectedFieldsOrdered<AnyColumn>,
214
+ row: unknown[],
215
+ joinsNotNullableMap: Record<string, boolean> | undefined
216
+ ): TResult {
217
+ const nullifyMap: Record<string, string | false> = {};
218
+
219
+ const result = columns.reduce<Record<string, any>>(
220
+ (acc, { path, field }, columnIndex) => {
221
+ let decoder: DriverValueDecoder<unknown, unknown>;
222
+ if (is(field, Column)) {
223
+ decoder = field;
224
+ } else if (is(field, SQL)) {
225
+ decoder = (field as SQLInternal).decoder;
226
+ } else {
227
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
228
+
229
+ if (is(col, PgCustomColumn)) {
230
+ decoder = col;
231
+ } else {
232
+ decoder = (field.sql as SQLInternal).decoder;
233
+ }
234
+ }
235
+ let node = acc;
236
+ for (const [pathChunkIndex, pathChunk] of path.entries()) {
237
+ if (pathChunkIndex < path.length - 1) {
238
+ if (!(pathChunk in node)) {
239
+ node[pathChunk] = {};
240
+ }
241
+ node = node[pathChunk];
242
+ continue;
243
+ }
244
+
245
+ const rawValue = normalizeInet(row[columnIndex]!);
246
+
247
+ const value = (node[pathChunk] =
248
+ rawValue === null ? null : mapDriverValue(decoder, rawValue));
249
+
250
+ if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
251
+ const objectName = path[0]!;
252
+ if (!(objectName in nullifyMap)) {
253
+ nullifyMap[objectName] =
254
+ value === null ? getTableName(field.table) : false;
255
+ } else if (
256
+ typeof nullifyMap[objectName] === 'string' &&
257
+ nullifyMap[objectName] !== getTableName(field.table)
258
+ ) {
259
+ nullifyMap[objectName] = false;
260
+ }
261
+ continue;
262
+ }
263
+
264
+ if (
265
+ joinsNotNullableMap &&
266
+ is(field, SQL.Aliased) &&
267
+ path.length === 2
268
+ ) {
269
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
270
+ const tableName = col?.table && getTableName(col?.table);
271
+
272
+ if (!tableName) {
273
+ continue;
274
+ }
275
+
276
+ const objectName = path[0]!;
277
+
278
+ if (!(objectName in nullifyMap)) {
279
+ nullifyMap[objectName] = value === null ? tableName : false;
280
+ continue;
281
+ }
282
+
283
+ if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
284
+ nullifyMap[objectName] = false;
285
+ }
286
+ continue;
287
+ }
288
+ }
289
+ return acc;
290
+ },
291
+ {}
292
+ );
293
+
294
+ if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
295
+ for (const [objectName, tableName] of Object.entries(nullifyMap)) {
296
+ if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
297
+ result[objectName] = null;
298
+ }
299
+ }
300
+ }
301
+
302
+ return result as TResult;
303
+ }
@@ -0,0 +1,67 @@
1
+ import {
2
+ Column,
3
+ SQL,
4
+ getTableName,
5
+ is,
6
+ sql,
7
+ } from 'drizzle-orm';
8
+ import type { SelectedFields } from 'drizzle-orm/pg-core';
9
+
10
+ function mapEntries(
11
+ obj: Record<string, unknown>,
12
+ prefix?: string,
13
+ fullJoin = false
14
+ ): Record<string, unknown> {
15
+ return Object.fromEntries(
16
+ Object.entries(obj)
17
+ .filter(([key]) => key !== 'enableRLS')
18
+ .map(([key, value]) => {
19
+ const qualified = prefix ? `${prefix}.${key}` : key;
20
+
21
+ if (fullJoin && is(value, Column)) {
22
+ return [
23
+ key,
24
+ sql`${value}`
25
+ .mapWith(value)
26
+ .as(`${getTableName(value.table)}.${value.name}`),
27
+ ];
28
+ }
29
+
30
+ if (fullJoin && is(value, SQL)) {
31
+ const col = value
32
+ .getSQL()
33
+ .queryChunks.find((chunk) => is(chunk, Column));
34
+
35
+ const tableName = col?.table && getTableName(col?.table);
36
+
37
+ return [key, value.as(tableName ? `${tableName}.${key}` : key)];
38
+ }
39
+
40
+ if (is(value, SQL) || is(value, Column)) {
41
+ const aliased =
42
+ is(value, SQL) ? value : sql`${value}`.mapWith(value);
43
+ return [key, aliased.as(qualified)];
44
+ }
45
+
46
+ if (is(value, SQL.Aliased)) {
47
+ return [key, value];
48
+ }
49
+
50
+ if (typeof value === 'object' && value !== null) {
51
+ return [
52
+ key,
53
+ mapEntries(value as Record<string, unknown>, qualified, fullJoin),
54
+ ];
55
+ }
56
+
57
+ return [key, value];
58
+ })
59
+ );
60
+ }
61
+
62
+ export function aliasFields(
63
+ fields: SelectedFields,
64
+ fullJoin = false
65
+ ): SelectedFields {
66
+ return mapEntries(fields, undefined, fullJoin) as SelectedFields;
67
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,3 @@
1
+ export { aliasFields } from './sql/selection.ts';
2
+ export { adaptArrayOperators, queryAdapter } from './sql/query-rewriters.ts';
3
+ export { mapResultRow } from './sql/result-mapper.ts';