@syncular/core 0.0.2-128 → 0.0.2-133
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/column-codecs.d.ts.map +1 -1
- package/dist/column-codecs.js +9 -2
- package/dist/column-codecs.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/kysely-column-codecs.d.ts +15 -0
- package/dist/kysely-column-codecs.d.ts.map +1 -0
- package/dist/kysely-column-codecs.js +466 -0
- package/dist/kysely-column-codecs.js.map +1 -0
- package/package.json +1 -1
- package/src/column-codecs.ts +6 -2
- package/src/index.ts +2 -2
- package/src/kysely-column-codecs.ts +590 -0
- package/dist/kysely-serialize.d.ts +0 -22
- package/dist/kysely-serialize.d.ts.map +0 -1
- package/dist/kysely-serialize.js +0 -147
- package/dist/kysely-serialize.js.map +0 -1
- package/src/kysely-serialize.ts +0 -214
|
@@ -0,0 +1,590 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type AliasNode,
|
|
3
|
+
type ColumnNode,
|
|
4
|
+
type ColumnUpdateNode,
|
|
5
|
+
type DeleteQueryNode,
|
|
6
|
+
type IdentifierNode,
|
|
7
|
+
type InsertQueryNode,
|
|
8
|
+
type KyselyPlugin,
|
|
9
|
+
type OperationNode,
|
|
10
|
+
OperationNodeTransformer,
|
|
11
|
+
type PluginTransformQueryArgs,
|
|
12
|
+
type PluginTransformResultArgs,
|
|
13
|
+
type PrimitiveValueListNode,
|
|
14
|
+
type QueryResult,
|
|
15
|
+
type ReferenceNode,
|
|
16
|
+
type RootOperationNode,
|
|
17
|
+
type SelectionNode,
|
|
18
|
+
type SelectQueryNode,
|
|
19
|
+
type TableNode,
|
|
20
|
+
type UnknownRow,
|
|
21
|
+
type UpdateQueryNode,
|
|
22
|
+
type ValueNode,
|
|
23
|
+
type ValuesNode,
|
|
24
|
+
} from 'kysely';
|
|
25
|
+
import {
|
|
26
|
+
type AnyColumnCodec,
|
|
27
|
+
applyCodecFromDbValue,
|
|
28
|
+
applyCodecToDbValue,
|
|
29
|
+
type ColumnCodecDialect,
|
|
30
|
+
type ColumnCodecSource,
|
|
31
|
+
type TableColumnCodecs,
|
|
32
|
+
toTableColumnCodecs,
|
|
33
|
+
} from './column-codecs';
|
|
34
|
+
|
|
35
|
+
interface ColumnCodecPluginOptions {
|
|
36
|
+
columnCodecs: ColumnCodecSource;
|
|
37
|
+
dialect?: ColumnCodecDialect;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface ColumnReference {
|
|
41
|
+
outputKey: string;
|
|
42
|
+
table: string;
|
|
43
|
+
column: string;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface QueryResultPlan {
|
|
47
|
+
explicit: ColumnReference[];
|
|
48
|
+
selectAllTables: string[];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function isIdentifierNode(
|
|
52
|
+
node: OperationNode | undefined
|
|
53
|
+
): node is IdentifierNode {
|
|
54
|
+
return node?.kind === 'IdentifierNode';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function isTableNode(node: OperationNode | undefined): node is TableNode {
|
|
58
|
+
return node?.kind === 'TableNode';
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function isAliasNode(node: OperationNode | undefined): node is AliasNode {
|
|
62
|
+
return node?.kind === 'AliasNode';
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function isColumnNode(node: OperationNode | undefined): node is ColumnNode {
|
|
66
|
+
return node?.kind === 'ColumnNode';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function isReferenceNode(
|
|
70
|
+
node: OperationNode | undefined
|
|
71
|
+
): node is ReferenceNode {
|
|
72
|
+
return node?.kind === 'ReferenceNode';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function isSelectionNode(
|
|
76
|
+
node: OperationNode | undefined
|
|
77
|
+
): node is SelectionNode {
|
|
78
|
+
return node?.kind === 'SelectionNode';
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function isPrimitiveValueListNode(
|
|
82
|
+
node: OperationNode | undefined
|
|
83
|
+
): node is PrimitiveValueListNode {
|
|
84
|
+
return node?.kind === 'PrimitiveValueListNode';
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function isValuesNode(node: OperationNode | undefined): node is ValuesNode {
|
|
88
|
+
return node?.kind === 'ValuesNode';
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function isValueNode(node: OperationNode | undefined): node is ValueNode {
|
|
92
|
+
return node?.kind === 'ValueNode';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function getIdentifierName(node?: OperationNode): string | undefined {
|
|
96
|
+
if (!isIdentifierNode(node)) return undefined;
|
|
97
|
+
return node?.name;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function getTableName(node?: OperationNode): string | undefined {
|
|
101
|
+
if (!isTableNode(node)) return undefined;
|
|
102
|
+
return getIdentifierName(node.table.identifier);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function getColumnName(node?: OperationNode): string | undefined {
|
|
106
|
+
if (!isColumnNode(node)) return undefined;
|
|
107
|
+
return getIdentifierName(node.column);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function getBaseTableFromAlias(node: AliasNode): string | undefined {
|
|
111
|
+
if (!isTableNode(node.node)) return undefined;
|
|
112
|
+
return getTableName(node.node);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function getAliasName(node: AliasNode): string | undefined {
|
|
116
|
+
return getIdentifierName(node.alias);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function tableAliasMapForSelect(node: SelectQueryNode): Map<string, string> {
|
|
120
|
+
const aliases = new Map<string, string>();
|
|
121
|
+
|
|
122
|
+
for (const fromItem of node.from?.froms ?? []) {
|
|
123
|
+
if (isTableNode(fromItem)) {
|
|
124
|
+
const tableName = getTableName(fromItem);
|
|
125
|
+
if (tableName) aliases.set(tableName, tableName);
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
if (isAliasNode(fromItem)) {
|
|
129
|
+
const tableName = getBaseTableFromAlias(fromItem);
|
|
130
|
+
const aliasName = getAliasName(fromItem);
|
|
131
|
+
if (tableName && aliasName) aliases.set(aliasName, tableName);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
for (const join of node.joins ?? []) {
|
|
136
|
+
const joinItem = join.table;
|
|
137
|
+
if (isTableNode(joinItem)) {
|
|
138
|
+
const tableName = getTableName(joinItem);
|
|
139
|
+
if (tableName) aliases.set(tableName, tableName);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
if (isAliasNode(joinItem)) {
|
|
143
|
+
const tableName = getBaseTableFromAlias(joinItem);
|
|
144
|
+
const aliasName = getAliasName(joinItem);
|
|
145
|
+
if (tableName && aliasName) aliases.set(aliasName, tableName);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return aliases;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function resolveTableName(args: {
|
|
153
|
+
refTable?: string;
|
|
154
|
+
aliases: Map<string, string>;
|
|
155
|
+
fallbackTable?: string;
|
|
156
|
+
}): string | undefined {
|
|
157
|
+
if (args.refTable) {
|
|
158
|
+
return args.aliases.get(args.refTable) ?? args.refTable;
|
|
159
|
+
}
|
|
160
|
+
if (args.fallbackTable) return args.fallbackTable;
|
|
161
|
+
if (args.aliases.size === 1) {
|
|
162
|
+
return args.aliases.values().next().value;
|
|
163
|
+
}
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function collectSelectionReferences(args: {
|
|
168
|
+
selections: readonly OperationNode[];
|
|
169
|
+
aliases: Map<string, string>;
|
|
170
|
+
fallbackTable?: string;
|
|
171
|
+
}): { explicit: ColumnReference[]; selectAllTables: string[] } {
|
|
172
|
+
const explicit: ColumnReference[] = [];
|
|
173
|
+
const selectAllTables: string[] = [];
|
|
174
|
+
|
|
175
|
+
for (const selectionCandidate of args.selections) {
|
|
176
|
+
if (!isSelectionNode(selectionCandidate)) continue;
|
|
177
|
+
const selectionNode = selectionCandidate;
|
|
178
|
+
const selection = selectionNode.selection;
|
|
179
|
+
|
|
180
|
+
if (selection.kind === 'SelectAllNode') {
|
|
181
|
+
const tableName = resolveTableName({
|
|
182
|
+
aliases: args.aliases,
|
|
183
|
+
fallbackTable: args.fallbackTable,
|
|
184
|
+
});
|
|
185
|
+
if (tableName) selectAllTables.push(tableName);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if (selection.kind === 'ReferenceNode') {
|
|
190
|
+
if (selection.column.kind === 'SelectAllNode') {
|
|
191
|
+
const tableRef = getTableName(selection.table);
|
|
192
|
+
const tableName = resolveTableName({
|
|
193
|
+
refTable: tableRef,
|
|
194
|
+
aliases: args.aliases,
|
|
195
|
+
fallbackTable: args.fallbackTable,
|
|
196
|
+
});
|
|
197
|
+
if (tableName) selectAllTables.push(tableName);
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const columnName = getColumnName(selection.column);
|
|
202
|
+
const tableRef = getTableName(selection.table);
|
|
203
|
+
const tableName = resolveTableName({
|
|
204
|
+
refTable: tableRef,
|
|
205
|
+
aliases: args.aliases,
|
|
206
|
+
fallbackTable: args.fallbackTable,
|
|
207
|
+
});
|
|
208
|
+
if (!columnName || !tableName) continue;
|
|
209
|
+
|
|
210
|
+
explicit.push({
|
|
211
|
+
outputKey: columnName,
|
|
212
|
+
table: tableName,
|
|
213
|
+
column: columnName,
|
|
214
|
+
});
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (selection.kind === 'AliasNode' && isReferenceNode(selection.node)) {
|
|
219
|
+
const outputKey = getIdentifierName(selection.alias);
|
|
220
|
+
if (!outputKey) continue;
|
|
221
|
+
|
|
222
|
+
const reference = selection.node;
|
|
223
|
+
if (reference.column.kind === 'SelectAllNode') {
|
|
224
|
+
const tableRef = getTableName(reference.table);
|
|
225
|
+
const tableName = resolveTableName({
|
|
226
|
+
refTable: tableRef,
|
|
227
|
+
aliases: args.aliases,
|
|
228
|
+
fallbackTable: args.fallbackTable,
|
|
229
|
+
});
|
|
230
|
+
if (tableName) selectAllTables.push(tableName);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const columnName = getColumnName(reference.column);
|
|
235
|
+
const tableRef = getTableName(reference.table);
|
|
236
|
+
const tableName = resolveTableName({
|
|
237
|
+
refTable: tableRef,
|
|
238
|
+
aliases: args.aliases,
|
|
239
|
+
fallbackTable: args.fallbackTable,
|
|
240
|
+
});
|
|
241
|
+
if (!columnName || !tableName) continue;
|
|
242
|
+
|
|
243
|
+
explicit.push({
|
|
244
|
+
outputKey,
|
|
245
|
+
table: tableName,
|
|
246
|
+
column: columnName,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
return { explicit, selectAllTables };
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function planForSelect(node: SelectQueryNode): QueryResultPlan | null {
|
|
255
|
+
if (!node.selections || node.selections.length === 0) return null;
|
|
256
|
+
const aliases = tableAliasMapForSelect(node);
|
|
257
|
+
const collected = collectSelectionReferences({
|
|
258
|
+
selections: node.selections,
|
|
259
|
+
aliases,
|
|
260
|
+
});
|
|
261
|
+
if (
|
|
262
|
+
collected.explicit.length === 0 &&
|
|
263
|
+
collected.selectAllTables.length === 0
|
|
264
|
+
) {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
return collected;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function planForInsert(node: InsertQueryNode): QueryResultPlan | null {
|
|
271
|
+
if (!node.returning || node.returning.selections.length === 0) return null;
|
|
272
|
+
const tableName = getTableName(node.into);
|
|
273
|
+
if (!tableName) return null;
|
|
274
|
+
const aliases = new Map<string, string>([[tableName, tableName]]);
|
|
275
|
+
const collected = collectSelectionReferences({
|
|
276
|
+
selections: node.returning.selections,
|
|
277
|
+
aliases,
|
|
278
|
+
fallbackTable: tableName,
|
|
279
|
+
});
|
|
280
|
+
if (
|
|
281
|
+
collected.explicit.length === 0 &&
|
|
282
|
+
collected.selectAllTables.length === 0
|
|
283
|
+
) {
|
|
284
|
+
return null;
|
|
285
|
+
}
|
|
286
|
+
return collected;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function planForUpdate(node: UpdateQueryNode): QueryResultPlan | null {
|
|
290
|
+
if (!node.returning || node.returning.selections.length === 0) return null;
|
|
291
|
+
const tableName = getTableName(node.table);
|
|
292
|
+
if (!tableName) return null;
|
|
293
|
+
const aliases = new Map<string, string>([[tableName, tableName]]);
|
|
294
|
+
const collected = collectSelectionReferences({
|
|
295
|
+
selections: node.returning.selections,
|
|
296
|
+
aliases,
|
|
297
|
+
fallbackTable: tableName,
|
|
298
|
+
});
|
|
299
|
+
if (
|
|
300
|
+
collected.explicit.length === 0 &&
|
|
301
|
+
collected.selectAllTables.length === 0
|
|
302
|
+
) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return collected;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function planForDelete(node: DeleteQueryNode): QueryResultPlan | null {
|
|
309
|
+
if (!node.returning || node.returning.selections.length === 0) return null;
|
|
310
|
+
const fromTables = node.from?.froms ?? [];
|
|
311
|
+
if (fromTables.length !== 1) return null;
|
|
312
|
+
const fromItem = fromTables[0];
|
|
313
|
+
if (!fromItem) return null;
|
|
314
|
+
|
|
315
|
+
let tableName: string | undefined;
|
|
316
|
+
if (isTableNode(fromItem)) {
|
|
317
|
+
tableName = getTableName(fromItem);
|
|
318
|
+
} else if (isAliasNode(fromItem)) {
|
|
319
|
+
tableName = getBaseTableFromAlias(fromItem);
|
|
320
|
+
}
|
|
321
|
+
if (!tableName) return null;
|
|
322
|
+
|
|
323
|
+
const aliases = new Map<string, string>([[tableName, tableName]]);
|
|
324
|
+
const collected = collectSelectionReferences({
|
|
325
|
+
selections: node.returning.selections,
|
|
326
|
+
aliases,
|
|
327
|
+
fallbackTable: tableName,
|
|
328
|
+
});
|
|
329
|
+
if (
|
|
330
|
+
collected.explicit.length === 0 &&
|
|
331
|
+
collected.selectAllTables.length === 0
|
|
332
|
+
) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return collected;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function buildResultPlan(node: RootOperationNode): QueryResultPlan | null {
|
|
339
|
+
if (node.kind === 'SelectQueryNode') return planForSelect(node);
|
|
340
|
+
if (node.kind === 'InsertQueryNode') return planForInsert(node);
|
|
341
|
+
if (node.kind === 'UpdateQueryNode') return planForUpdate(node);
|
|
342
|
+
if (node.kind === 'DeleteQueryNode') return planForDelete(node);
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
function cacheKey(table: string, columns: readonly string[]): string {
|
|
347
|
+
const sorted = [...columns].sort().join('\u0000');
|
|
348
|
+
return `${table}\u0001${sorted}`;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
class ColumnCodecsTransformer extends OperationNodeTransformer {
|
|
352
|
+
readonly #columnCodecs: ColumnCodecSource;
|
|
353
|
+
readonly #dialect: ColumnCodecDialect;
|
|
354
|
+
readonly #tableCodecsCache = new Map<string, TableColumnCodecs>();
|
|
355
|
+
#currentUpdateTable: string | null = null;
|
|
356
|
+
|
|
357
|
+
constructor(options: ColumnCodecPluginOptions) {
|
|
358
|
+
super();
|
|
359
|
+
this.#columnCodecs = options.columnCodecs;
|
|
360
|
+
this.#dialect = options.dialect ?? 'sqlite';
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
resolveTableCodecs(
|
|
364
|
+
table: string,
|
|
365
|
+
columns: readonly string[]
|
|
366
|
+
): TableColumnCodecs {
|
|
367
|
+
const key = cacheKey(table, columns);
|
|
368
|
+
const cached = this.#tableCodecsCache.get(key);
|
|
369
|
+
if (cached) return cached;
|
|
370
|
+
const resolved = toTableColumnCodecs(table, this.#columnCodecs, columns, {
|
|
371
|
+
dialect: this.#dialect,
|
|
372
|
+
});
|
|
373
|
+
this.#tableCodecsCache.set(key, resolved);
|
|
374
|
+
return resolved;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
protected override transformInsertQuery(
|
|
378
|
+
node: InsertQueryNode
|
|
379
|
+
): InsertQueryNode {
|
|
380
|
+
const transformed = super.transformInsertQuery(node);
|
|
381
|
+
if (!transformed.columns || transformed.columns.length === 0) {
|
|
382
|
+
return transformed;
|
|
383
|
+
}
|
|
384
|
+
if (!isValuesNode(transformed.values)) {
|
|
385
|
+
return transformed;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const tableName = getTableName(transformed.into);
|
|
389
|
+
if (!tableName) return transformed;
|
|
390
|
+
|
|
391
|
+
const columns = transformed.columns
|
|
392
|
+
.map((columnNode) => getColumnName(columnNode))
|
|
393
|
+
.filter((columnName): columnName is string => Boolean(columnName));
|
|
394
|
+
if (columns.length === 0) return transformed;
|
|
395
|
+
|
|
396
|
+
const tableCodecs = this.resolveTableCodecs(tableName, columns);
|
|
397
|
+
|
|
398
|
+
let didChange = false;
|
|
399
|
+
const nextValueRows = transformed.values.values.map((valueNode) => {
|
|
400
|
+
if (!isPrimitiveValueListNode(valueNode)) return valueNode;
|
|
401
|
+
|
|
402
|
+
const nextValues = valueNode.values.map(
|
|
403
|
+
(value: unknown, index: number) => {
|
|
404
|
+
const columnName = columns[index];
|
|
405
|
+
if (!columnName) return value;
|
|
406
|
+
const codec = tableCodecs[columnName];
|
|
407
|
+
if (!codec) return value;
|
|
408
|
+
const converted = applyCodecToDbValue(codec, value, this.#dialect);
|
|
409
|
+
if (converted !== value) {
|
|
410
|
+
didChange = true;
|
|
411
|
+
}
|
|
412
|
+
return converted;
|
|
413
|
+
}
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
if (!didChange) return valueNode;
|
|
417
|
+
return {
|
|
418
|
+
...valueNode,
|
|
419
|
+
values: nextValues,
|
|
420
|
+
};
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
if (!didChange) return transformed;
|
|
424
|
+
|
|
425
|
+
const nextValuesNode: ValuesNode = {
|
|
426
|
+
...transformed.values,
|
|
427
|
+
values: nextValueRows,
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
...transformed,
|
|
432
|
+
values: nextValuesNode,
|
|
433
|
+
};
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
protected override transformUpdateQuery(
|
|
437
|
+
node: UpdateQueryNode
|
|
438
|
+
): UpdateQueryNode {
|
|
439
|
+
const previousTable = this.#currentUpdateTable;
|
|
440
|
+
this.#currentUpdateTable = getTableName(node.table) ?? null;
|
|
441
|
+
try {
|
|
442
|
+
return super.transformUpdateQuery(node);
|
|
443
|
+
} finally {
|
|
444
|
+
this.#currentUpdateTable = previousTable;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
protected override transformColumnUpdate(
|
|
449
|
+
node: ColumnUpdateNode,
|
|
450
|
+
queryId?: { readonly queryId: string }
|
|
451
|
+
): ColumnUpdateNode {
|
|
452
|
+
const transformed = super.transformColumnUpdate(node, queryId);
|
|
453
|
+
if (!this.#currentUpdateTable) return transformed;
|
|
454
|
+
if (!isValueNode(transformed.value)) return transformed;
|
|
455
|
+
|
|
456
|
+
const columnName = getColumnName(transformed.column);
|
|
457
|
+
if (!columnName) return transformed;
|
|
458
|
+
|
|
459
|
+
const tableCodecs = this.resolveTableCodecs(this.#currentUpdateTable, [
|
|
460
|
+
columnName,
|
|
461
|
+
]);
|
|
462
|
+
const codec = tableCodecs[columnName];
|
|
463
|
+
if (!codec) return transformed;
|
|
464
|
+
|
|
465
|
+
const valueNode = transformed.value;
|
|
466
|
+
const converted = applyCodecToDbValue(
|
|
467
|
+
codec,
|
|
468
|
+
valueNode.value,
|
|
469
|
+
this.#dialect
|
|
470
|
+
);
|
|
471
|
+
if (converted === valueNode.value) return transformed;
|
|
472
|
+
|
|
473
|
+
const nextValueNode: ValueNode = {
|
|
474
|
+
...valueNode,
|
|
475
|
+
value: converted,
|
|
476
|
+
};
|
|
477
|
+
return {
|
|
478
|
+
...transformed,
|
|
479
|
+
value: nextValueNode,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
export class ColumnCodecsPlugin implements KyselyPlugin {
|
|
485
|
+
readonly #dialect: ColumnCodecDialect;
|
|
486
|
+
readonly #transformer: ColumnCodecsTransformer;
|
|
487
|
+
readonly #resultPlans = new WeakMap<object, QueryResultPlan>();
|
|
488
|
+
readonly #columnCodecs: ColumnCodecSource;
|
|
489
|
+
readonly #resultCodecCache = new Map<string, TableColumnCodecs>();
|
|
490
|
+
|
|
491
|
+
constructor(options: ColumnCodecPluginOptions) {
|
|
492
|
+
this.#dialect = options.dialect ?? 'sqlite';
|
|
493
|
+
this.#columnCodecs = options.columnCodecs;
|
|
494
|
+
this.#transformer = new ColumnCodecsTransformer(options);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
transformQuery({
|
|
498
|
+
node,
|
|
499
|
+
queryId,
|
|
500
|
+
}: PluginTransformQueryArgs): RootOperationNode {
|
|
501
|
+
const transformed = this.#transformer.transformNode(node);
|
|
502
|
+
const plan = buildResultPlan(transformed);
|
|
503
|
+
if (plan) {
|
|
504
|
+
this.#resultPlans.set(queryId, plan);
|
|
505
|
+
}
|
|
506
|
+
return transformed;
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
async transformResult({
|
|
510
|
+
result,
|
|
511
|
+
queryId,
|
|
512
|
+
}: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>> {
|
|
513
|
+
const plan = this.#resultPlans.get(queryId);
|
|
514
|
+
if (!plan) return result;
|
|
515
|
+
if (!result.rows || result.rows.length === 0) return result;
|
|
516
|
+
|
|
517
|
+
const rows = result.rows.map((row) => this.#transformRow(row, plan));
|
|
518
|
+
return {
|
|
519
|
+
...result,
|
|
520
|
+
rows,
|
|
521
|
+
};
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
#resolveTableCodecs(
|
|
525
|
+
table: string,
|
|
526
|
+
columns: readonly string[]
|
|
527
|
+
): TableColumnCodecs {
|
|
528
|
+
const key = cacheKey(table, columns);
|
|
529
|
+
const cached = this.#resultCodecCache.get(key);
|
|
530
|
+
if (cached) return cached;
|
|
531
|
+
const resolved = toTableColumnCodecs(table, this.#columnCodecs, columns, {
|
|
532
|
+
dialect: this.#dialect,
|
|
533
|
+
});
|
|
534
|
+
this.#resultCodecCache.set(key, resolved);
|
|
535
|
+
return resolved;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
#transformRow(row: UnknownRow, plan: QueryResultPlan): UnknownRow {
|
|
539
|
+
const source = row as Record<string, unknown>;
|
|
540
|
+
const target: Record<string, unknown> = { ...source };
|
|
541
|
+
const rowColumns = Object.keys(source);
|
|
542
|
+
|
|
543
|
+
if (plan.selectAllTables.length > 0) {
|
|
544
|
+
const codecCandidates = new Map<string, AnyColumnCodec>();
|
|
545
|
+
const ambiguousColumns = new Set<string>();
|
|
546
|
+
|
|
547
|
+
for (const table of plan.selectAllTables) {
|
|
548
|
+
const tableCodecs = this.#resolveTableCodecs(table, rowColumns);
|
|
549
|
+
for (const [column, codec] of Object.entries(tableCodecs)) {
|
|
550
|
+
if (!(column in source)) continue;
|
|
551
|
+
const existing = codecCandidates.get(column);
|
|
552
|
+
if (existing && existing !== codec) {
|
|
553
|
+
ambiguousColumns.add(column);
|
|
554
|
+
continue;
|
|
555
|
+
}
|
|
556
|
+
codecCandidates.set(column, codec);
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
for (const [column, codec] of codecCandidates.entries()) {
|
|
561
|
+
if (ambiguousColumns.has(column)) continue;
|
|
562
|
+
target[column] = applyCodecFromDbValue(
|
|
563
|
+
codec,
|
|
564
|
+
target[column],
|
|
565
|
+
this.#dialect
|
|
566
|
+
);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
for (const ref of plan.explicit) {
|
|
571
|
+
if (!(ref.outputKey in source)) continue;
|
|
572
|
+
const tableCodecs = this.#resolveTableCodecs(ref.table, [ref.column]);
|
|
573
|
+
const codec = tableCodecs[ref.column];
|
|
574
|
+
if (!codec) continue;
|
|
575
|
+
target[ref.outputKey] = applyCodecFromDbValue(
|
|
576
|
+
codec,
|
|
577
|
+
target[ref.outputKey],
|
|
578
|
+
this.#dialect
|
|
579
|
+
);
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
return target;
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
export function createColumnCodecsPlugin(
|
|
587
|
+
options: ColumnCodecPluginOptions
|
|
588
|
+
): ColumnCodecsPlugin {
|
|
589
|
+
return new ColumnCodecsPlugin(options);
|
|
590
|
+
}
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { type KyselyPlugin, type PluginTransformQueryArgs, type PluginTransformResultArgs, type QueryResult, type RootOperationNode, type UnknownRow } from 'kysely';
|
|
2
|
-
type Serializer = (parameter: unknown) => unknown;
|
|
3
|
-
type Deserializer = (parameter: unknown) => unknown;
|
|
4
|
-
declare class BaseSerializePlugin implements KyselyPlugin {
|
|
5
|
-
#private;
|
|
6
|
-
/**
|
|
7
|
-
* Base class for {@link SerializePlugin}, without default options.
|
|
8
|
-
*/
|
|
9
|
-
constructor(serializer: Serializer, deserializer: Deserializer, skipNodeKind: Array<RootOperationNode['kind']>);
|
|
10
|
-
transformQuery({ node, queryId }: PluginTransformQueryArgs): RootOperationNode;
|
|
11
|
-
transformResult({ result, queryId }: PluginTransformResultArgs): Promise<QueryResult<UnknownRow>>;
|
|
12
|
-
}
|
|
13
|
-
interface SerializePluginOptions {
|
|
14
|
-
serializer?: Serializer;
|
|
15
|
-
deserializer?: Deserializer;
|
|
16
|
-
skipNodeKind?: Array<RootOperationNode['kind']>;
|
|
17
|
-
}
|
|
18
|
-
export declare class SerializePlugin extends BaseSerializePlugin {
|
|
19
|
-
constructor(options?: SerializePluginOptions);
|
|
20
|
-
}
|
|
21
|
-
export {};
|
|
22
|
-
//# sourceMappingURL=kysely-serialize.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"kysely-serialize.d.ts","sourceRoot":"","sources":["../src/kysely-serialize.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,YAAY,EAEjB,KAAK,wBAAwB,EAC7B,KAAK,yBAAyB,EAE9B,KAAK,WAAW,EAChB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EAEhB,MAAM,QAAQ,CAAC;AAEhB,KAAK,UAAU,GAAG,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC;AAClD,KAAK,YAAY,GAAG,CAAC,SAAS,EAAE,OAAO,KAAK,OAAO,CAAC;AA4HpD,cAAM,mBAAoB,YAAW,YAAY;;IAM/C;;OAEG;IACH,YACE,UAAU,EAAE,UAAU,EACtB,YAAY,EAAE,YAAY,EAC1B,YAAY,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,EAW/C;IAED,cAAc,CAAC,EACb,IAAI,EACJ,OAAO,EACR,EAAE,wBAAwB,GAAG,iBAAiB,CAM9C;IAEK,eAAe,CAAC,EACpB,MAAM,EACN,OAAO,EACR,EAAE,yBAAyB,GAAG,OAAO,CAAC,WAAW,CAAC,UAAU,CAAC,CAAC,CAK9D;CAcF;AAED,UAAU,sBAAsB;IAC9B,UAAU,CAAC,EAAE,UAAU,CAAC;IACxB,YAAY,CAAC,EAAE,YAAY,CAAC;IAC5B,YAAY,CAAC,EAAE,KAAK,CAAC,iBAAiB,CAAC,MAAM,CAAC,CAAC,CAAC;CACjD;AAED,qBAAa,eAAgB,SAAQ,mBAAmB;IACtD,YAAY,OAAO,GAAE,sBAA2B,EAO/C;CACF"}
|