@lakeql/response-transformer 0.1.7 → 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.
- package/dist/index.d.mts +113 -140
- package/dist/index.mjs +1 -1
- package/package.json +11 -13
package/dist/index.d.mts
CHANGED
|
@@ -1,163 +1,136 @@
|
|
|
1
|
-
|
|
1
|
+
import { AdapterName } from "@lakeql/datasource";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
3
|
+
* Adapter-neutral, schema-aware transformation for keyed raw records.
|
|
4
|
+
*
|
|
5
|
+
* Runs after an adapter's `ResponseNormalizer` has decoded the wire protocol
|
|
6
|
+
* into keyed records. Walks each record against a schema, converting nested
|
|
7
|
+
* objects and arrays, applying field mapping, and normalizing null-ish scalar
|
|
8
|
+
* values. It never branches on adapter names and never decodes date/timestamp
|
|
9
|
+
* or timezone information — that stays adapter-owned in the normalizer.
|
|
10
|
+
*
|
|
11
|
+
* @module
|
|
5
12
|
*/
|
|
6
|
-
type JSONSchema7TypeName = "string" | "number" | "integer" | "boolean" | "object" | "array" | "null";
|
|
7
13
|
/**
|
|
8
|
-
*
|
|
9
|
-
|
|
14
|
+
* Minimal JSON-Schema subset interpreted by {@link transformRecords}.
|
|
15
|
+
*/
|
|
16
|
+
interface TransformSchema {
|
|
17
|
+
/** JSON-Schema type. Only `"object"` and `"array"` trigger recursion. */
|
|
18
|
+
type?: string;
|
|
19
|
+
/** Property schemas for `"object"` types, keyed by SOURCE field name. */
|
|
20
|
+
properties?: Record<string, TransformSchema>;
|
|
21
|
+
/** Item schema for `"array"` types. */
|
|
22
|
+
items?: TransformSchema;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Input for {@link transformRecords}.
|
|
10
26
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
27
|
+
interface TransformRecordsInput {
|
|
28
|
+
/** Already-keyed records produced by an adapter normalizer. */
|
|
29
|
+
records: readonly Record<string, unknown>[];
|
|
30
|
+
/** Schema used to interpret nested object/array fields. */
|
|
31
|
+
schema: TransformSchema;
|
|
32
|
+
/**
|
|
33
|
+
* Field renaming, direction SOURCE key → TARGET key.
|
|
34
|
+
*
|
|
35
|
+
* Top-level fields are typically already renamed by the query dialect via
|
|
36
|
+
* SQL aliases, so this mapping is primarily relevant for fields nested
|
|
37
|
+
* inside object/array structures where SQL aliases cannot reach.
|
|
38
|
+
*/
|
|
39
|
+
fieldMapping?: Readonly<Record<string, string>>;
|
|
14
40
|
}
|
|
15
|
-
type JSONSchema7Array = JSONSchema7Type[];
|
|
16
41
|
/**
|
|
17
|
-
*
|
|
42
|
+
* Transforms keyed raw records against a schema.
|
|
18
43
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
44
|
+
* @param input - Records, schema, and optional field mapping.
|
|
45
|
+
* @returns A new array of transformed records.
|
|
46
|
+
*/
|
|
47
|
+
export declare function transformRecords(input: TransformRecordsInput): Record<string, unknown>[];
|
|
48
|
+
/**
|
|
49
|
+
* Neutral input shape consumed by {@link ResponseNormalizer.normalize}.
|
|
24
50
|
*
|
|
25
|
-
*
|
|
51
|
+
* Contains the raw query response rows and metadata needed to convert
|
|
52
|
+
* backend-specific values into the shared {@link NormalizedResponse} shape.
|
|
26
53
|
*/
|
|
27
|
-
|
|
54
|
+
interface ResponseNormalizationInput {
|
|
55
|
+
/** Raw rows returned by the data source query. */
|
|
56
|
+
response: readonly unknown[];
|
|
57
|
+
/** Field names selected by the caller. */
|
|
58
|
+
selectFields: readonly string[];
|
|
59
|
+
/** Mapping from source field names to target field names. */
|
|
60
|
+
transformFields: Readonly<Record<string, string>>;
|
|
61
|
+
/** Field names that carry date/timestamp values. */
|
|
62
|
+
dateFields: readonly string[];
|
|
63
|
+
/** Backend-specific schema used for value interpretation. */
|
|
64
|
+
schema: unknown;
|
|
65
|
+
}
|
|
28
66
|
/**
|
|
29
|
-
*
|
|
30
|
-
*
|
|
67
|
+
* Result shape produced by {@link ResponseNormalizer.normalize}.
|
|
68
|
+
*
|
|
69
|
+
* @template T - Shape of each record in the returned data array.
|
|
31
70
|
*/
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.2
|
|
51
|
-
*/
|
|
52
|
-
multipleOf?: number | undefined;
|
|
53
|
-
maximum?: number | undefined;
|
|
54
|
-
exclusiveMaximum?: number | undefined;
|
|
55
|
-
minimum?: number | undefined;
|
|
56
|
-
exclusiveMinimum?: number | undefined;
|
|
57
|
-
/**
|
|
58
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.3
|
|
59
|
-
*/
|
|
60
|
-
maxLength?: number | undefined;
|
|
61
|
-
minLength?: number | undefined;
|
|
62
|
-
pattern?: string | undefined;
|
|
63
|
-
/**
|
|
64
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.4
|
|
65
|
-
*/
|
|
66
|
-
items?: JSONSchema7Definition | JSONSchema7Definition[] | undefined;
|
|
67
|
-
additionalItems?: JSONSchema7Definition | undefined;
|
|
68
|
-
maxItems?: number | undefined;
|
|
69
|
-
minItems?: number | undefined;
|
|
70
|
-
uniqueItems?: boolean | undefined;
|
|
71
|
-
contains?: JSONSchema7Definition | undefined;
|
|
72
|
-
/**
|
|
73
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.5
|
|
74
|
-
*/
|
|
75
|
-
maxProperties?: number | undefined;
|
|
76
|
-
minProperties?: number | undefined;
|
|
77
|
-
required?: string[] | undefined;
|
|
78
|
-
properties?: Record<string, JSONSchema7Definition> | undefined;
|
|
79
|
-
patternProperties?: Record<string, JSONSchema7Definition> | undefined;
|
|
80
|
-
additionalProperties?: JSONSchema7Definition | undefined;
|
|
81
|
-
dependencies?: Record<string, JSONSchema7Definition | string[]> | undefined;
|
|
82
|
-
propertyNames?: JSONSchema7Definition | undefined;
|
|
83
|
-
/**
|
|
84
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.6
|
|
85
|
-
*/
|
|
86
|
-
if?: JSONSchema7Definition | undefined;
|
|
87
|
-
then?: JSONSchema7Definition | undefined;
|
|
88
|
-
else?: JSONSchema7Definition | undefined;
|
|
89
|
-
/**
|
|
90
|
-
* @see https://tools.ietf.org/html/draft-handrews-json-schema-validation-01#section-6.7
|
|
91
|
-
*/
|
|
92
|
-
allOf?: JSONSchema7Definition[] | undefined;
|
|
93
|
-
anyOf?: JSONSchema7Definition[] | undefined;
|
|
94
|
-
oneOf?: JSONSchema7Definition[] | undefined;
|
|
95
|
-
not?: JSONSchema7Definition | undefined;
|
|
71
|
+
interface NormalizedResponse<T> {
|
|
72
|
+
/** Total number of records matching the query, before paging. */
|
|
73
|
+
totalCount: number;
|
|
74
|
+
/** Transformed records for the current page. */
|
|
75
|
+
data: T[];
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Adapter-neutral contract for converting raw query response rows into a
|
|
79
|
+
* {@link NormalizedResponse}.
|
|
80
|
+
*
|
|
81
|
+
* Concrete adapters implement this interface to own backend-specific value
|
|
82
|
+
* conversion, schema interpretation, and total-count extraction. The shared
|
|
83
|
+
* SQL read strategy passes neutral input through unchanged and never branches
|
|
84
|
+
* on adapter names.
|
|
85
|
+
*/
|
|
86
|
+
interface ResponseNormalizer {
|
|
87
|
+
/** Open identifier of the adapter that owns this normalizer. */
|
|
88
|
+
readonly adapterName: AdapterName;
|
|
96
89
|
/**
|
|
97
|
-
*
|
|
90
|
+
* Convert raw response rows into a normalized result.
|
|
91
|
+
*
|
|
92
|
+
* @template T - Shape of each record in the returned data array.
|
|
93
|
+
* @param input - Neutral normalization parameters.
|
|
94
|
+
* @returns Normalized response containing total count and typed data.
|
|
98
95
|
*/
|
|
99
|
-
|
|
96
|
+
normalize: <T>(input: ResponseNormalizationInput) => NormalizedResponse<T>;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Options for {@link createCteResponseNormalizer}.
|
|
100
|
+
*/
|
|
101
|
+
interface CteResponseNormalizerOptions {
|
|
102
|
+
/** Open identifier of the adapter that owns this normalizer. */
|
|
103
|
+
adapterName: AdapterName;
|
|
100
104
|
/**
|
|
101
|
-
*
|
|
105
|
+
* Predicate deciding whether a non-null sentinel value marks a real data
|
|
106
|
+
* row. This is adapter-specific and MUST stay injected: Trino accepts only
|
|
107
|
+
* the boolean `true`, while Fabric's TDS driver returns `1`. Widening one
|
|
108
|
+
* adapter's predicate to the other's would change validation behavior.
|
|
102
109
|
*/
|
|
103
|
-
|
|
104
|
-
contentEncoding?: string | undefined;
|
|
110
|
+
isPresentSentinel: (value: unknown) => boolean;
|
|
105
111
|
/**
|
|
106
|
-
*
|
|
112
|
+
* Human-readable description of the accepted sentinel values, used in the
|
|
113
|
+
* error thrown for an invalid sentinel (e.g. `"true"` or `"true/1"`).
|
|
107
114
|
*/
|
|
108
|
-
|
|
115
|
+
acceptedSentinelDescription: string;
|
|
109
116
|
/**
|
|
110
|
-
*
|
|
117
|
+
* Strict plain-object guard. Injected because each adapter already ships its
|
|
118
|
+
* own (identical) implementation; passing it keeps this package free of a
|
|
119
|
+
* dependency on either adapter's `sql-helpers`.
|
|
111
120
|
*/
|
|
112
|
-
|
|
113
|
-
description?: string | undefined;
|
|
114
|
-
default?: JSONSchema7Type | undefined;
|
|
115
|
-
readOnly?: boolean | undefined;
|
|
116
|
-
writeOnly?: boolean | undefined;
|
|
117
|
-
examples?: JSONSchema7Type | undefined;
|
|
118
|
-
}
|
|
119
|
-
//#endregion
|
|
120
|
-
//#region src/index.d.ts
|
|
121
|
-
interface TransformInput {
|
|
122
|
-
data: Record<string, unknown>;
|
|
123
|
-
definition: JSONSchema7;
|
|
124
|
-
transformFields?: Record<string, string>;
|
|
125
|
-
dateFields?: string[];
|
|
126
|
-
utcDates?: string[];
|
|
121
|
+
isPlainObject: (value: unknown) => boolean;
|
|
127
122
|
}
|
|
128
|
-
interface TransformArrayInput {
|
|
129
|
-
data: Record<string, unknown>[];
|
|
130
|
-
definition: JSONSchema7;
|
|
131
|
-
transformFields?: Record<string, string>;
|
|
132
|
-
dateFields?: string[];
|
|
133
|
-
}
|
|
134
|
-
declare const transform: ({ data, definition, transformFields, dateFields, utcDates }: TransformInput) => Record<string, unknown>;
|
|
135
|
-
declare const transformArray: ({ data, definition, transformFields, dateFields }: TransformArrayInput) => Record<string, unknown>[];
|
|
136
|
-
declare const transformObject: ({ data, definition, transformFields, dateFields }: TransformInput) => Record<string, unknown>;
|
|
137
123
|
/**
|
|
138
|
-
*
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
values: unknown[];
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* Converts the trino response element ( array of unknown ) to an object
|
|
148
|
-
* based on the given keys. This function expects that
|
|
149
|
-
* amount of keys matches the amount of value elements
|
|
124
|
+
* Creates a {@link ResponseNormalizer} for the shared CTE query shape.
|
|
125
|
+
*
|
|
126
|
+
* The returned normalizer extracts and validates `total_records`, drops the
|
|
127
|
+
* synthetic count-only row and the internal metadata columns, converts date
|
|
128
|
+
* fields from seconds to milliseconds, and preserves all other values. The
|
|
129
|
+
* only adapter-specific behavior is the sentinel predicate.
|
|
150
130
|
*
|
|
151
|
-
* @
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* const convert = convertTrinoResponse({keys, values})
|
|
155
|
-
* // Result:
|
|
156
|
-
* //[{"fieldName1": "value1", "fieldName2": 42}]
|
|
157
|
-
* @param keys string[]
|
|
158
|
-
* @param values unknown[]
|
|
159
|
-
* @returns Record<string, unknown>
|
|
131
|
+
* @param options - Adapter name, sentinel predicate and description, and the
|
|
132
|
+
* plain-object guard.
|
|
133
|
+
* @returns A normalizer implementing the neutral {@link ResponseNormalizer}.
|
|
160
134
|
*/
|
|
161
|
-
declare function
|
|
162
|
-
|
|
163
|
-
export { ConvertTrinoResponseProps, TransformArrayInput, TransformInput, convertTrinoResponse, transform, transformArray, transformObject };
|
|
135
|
+
export declare function createCteResponseNormalizer(options: CteResponseNormalizerOptions): ResponseNormalizer;
|
|
136
|
+
export type { CteResponseNormalizerOptions, NormalizedResponse, ResponseNormalizationInput, ResponseNormalizer, TransformRecordsInput, TransformSchema };
|
package/dist/index.mjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
function e(e){return e===`object`||e===`array`}function t(e,t,r){let i=t.properties??{},a={};for(let t of Object.keys(i)){let o=i[t];if(o===void 0)continue;let s=r[t]??t;a[s]=n(e[t],o,r)}return a}function n(r,i,a){if(i.type===`object`)return r==null?null:typeof r!=`object`||Array.isArray(r)?r:t(r,i,a);if(i.type===`array`){if(r==null)return null;if(!Array.isArray(r))return r;let t=i.items;return t===void 0||!e(t.type)?r:r.map(e=>n(e,t,a))}return r??null}function r(e){let{records:n,schema:r,fieldMapping:i={}}=e;return n.map(e=>t(e,r,i))}const i=`total_records`,a=`__lakeql_record_present`;function o(e){if(e==null)throw Error(`Malformed count metadata: total_records is absent or null`);if(typeof e!=`number`)throw TypeError(`Malformed count metadata: total_records must be a number, got ${typeof e}`);if(!Number.isFinite(e))throw TypeError(`Malformed count metadata: total_records must be finite, got ${String(e)}`);if(e<0)throw RangeError(`Malformed count metadata: total_records must be non-negative, got ${e}`);if(!Number.isInteger(e))throw TypeError(`Malformed count metadata: total_records must be an integer, got ${e}`);return e}function s(e,t){if(e===null)return null;if(typeof e!=`number`)throw TypeError(`Invalid date field "${t}": expected a finite number, got ${typeof e}`);if(!Number.isFinite(e))throw TypeError(`Invalid date field "${t}": expected a finite number, got ${String(e)}`);return e*1e3}function c(e,t,n){if(!n(e))throw Error(`Malformed adapter response: row at index ${t} must be a non-null plain object, got ${e===null?`null`:typeof e}`);return e}function l(e){let{adapterName:t,isPresentSentinel:n,acceptedSentinelDescription:r,isPlainObject:l}=e;return{adapterName:t,normalize(e){let{response:t,dateFields:u}=e;if(t.length===0)return{totalCount:0,data:[]};let d=o(c(t[0],0,l)[i]),f=new Set(u),p=[],m=0;for(let e of t){let t=c(e,m,l);m+=1;let o=t[a];if(o===null)continue;if(!n(o))throw Error(`Malformed adapter response: "${a}" must be ${r} for data rows or null for synthetic rows, got ${typeof o==`string`?`"${o}"`:String(o)}`);let u={};for(let e of Object.keys(t))e!==i&&e!==a&&(u[e]=f.has(e)?s(t[e],e):t[e]);p.push(u)}return{totalCount:d,data:p}}}}export{l as createCteResponseNormalizer,r as transformRecords};
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lakeql/response-transformer",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Adapter-neutral, schema-aware transformation of keyed records for LakeQL",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"lakeql",
|
|
7
7
|
"mapping",
|
|
8
8
|
"response",
|
|
9
|
-
"
|
|
10
|
-
"
|
|
9
|
+
"schema",
|
|
10
|
+
"transform"
|
|
11
11
|
],
|
|
12
12
|
"homepage": "https://lakeql.dev",
|
|
13
13
|
"license": "Apache-2.0",
|
|
@@ -39,18 +39,15 @@
|
|
|
39
39
|
}
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@
|
|
42
|
+
"@lakeql/datasource": "1.0.0"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
|
-
"@
|
|
46
|
-
"@
|
|
47
|
-
"@
|
|
48
|
-
"tsdown": "0.
|
|
49
|
-
"tsx": "4.23.1",
|
|
45
|
+
"@lakeql/tsconfig": "0.1.0",
|
|
46
|
+
"@lakeql/vitest-config": "0.1.0",
|
|
47
|
+
"@types/node": "24.19.2",
|
|
48
|
+
"tsdown": "0.23.7",
|
|
50
49
|
"typescript": "6.0.3",
|
|
51
|
-
"
|
|
52
|
-
"vitest": "4.1.10",
|
|
53
|
-
"@lakeql/tsconfig": "0.1.0"
|
|
50
|
+
"vitest": "5.1.0"
|
|
54
51
|
},
|
|
55
52
|
"scripts": {
|
|
56
53
|
"build": "tsdown",
|
|
@@ -58,6 +55,7 @@
|
|
|
58
55
|
"clean:cache": "git clean -xdf .cache .turbo",
|
|
59
56
|
"clean:dist": "git clean -xdf dist",
|
|
60
57
|
"dev": "tsdown --watch",
|
|
58
|
+
"test": "vitest run",
|
|
61
59
|
"typecheck": "tsc --noEmit"
|
|
62
60
|
}
|
|
63
61
|
}
|