@uniqu/core 0.1.0 → 0.1.1
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.cjs +8 -0
- package/dist/index.d.ts +46 -7
- package/dist/index.mjs +8 -0
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -72,6 +72,14 @@ function isPrimitive(x) {
|
|
|
72
72
|
else capture(entry.$field, entry.$fn);
|
|
73
73
|
else for (const field of Object.keys(controls.$select)) capture(field, "$select");
|
|
74
74
|
if (controls?.$groupBy) for (const field of controls.$groupBy) capture(field, "$groupBy");
|
|
75
|
+
if (controls?.$having) walkFilter(controls.$having, {
|
|
76
|
+
comparison(field) {
|
|
77
|
+
capture(field, "$having");
|
|
78
|
+
},
|
|
79
|
+
and() {},
|
|
80
|
+
or() {},
|
|
81
|
+
not() {}
|
|
82
|
+
});
|
|
75
83
|
if (controls?.$sort) for (const field of Object.keys(controls.$sort)) capture(field, "$order");
|
|
76
84
|
if (controls?.$with) for (const entry of controls.$with) {
|
|
77
85
|
if (typeof entry === "string") {
|
package/dist/index.d.ts
CHANGED
|
@@ -67,14 +67,14 @@ type LogicalNode<T = Record<string, unknown>> = {
|
|
|
67
67
|
};
|
|
68
68
|
/** Known aggregate function names. Consumers may support additional functions via the (string & {}) escape hatch. */
|
|
69
69
|
type AggregateFn = 'sum' | 'count' | 'avg' | 'min' | 'max';
|
|
70
|
-
/** A single aggregate function call within $select. */
|
|
71
|
-
interface AggregateExpr {
|
|
70
|
+
/** A single aggregate function call within $select. Generic params preserve literal types for result inference. */
|
|
71
|
+
interface AggregateExpr<Fn extends string = AggregateFn | (string & {}), Field extends string = string, Alias extends string = string> {
|
|
72
72
|
/** Function name (sum, count, avg, min, max, or custom). */
|
|
73
|
-
$fn:
|
|
73
|
+
$fn: Fn;
|
|
74
74
|
/** Field to aggregate. '*' for count(*). */
|
|
75
|
-
$field:
|
|
75
|
+
$field: Field;
|
|
76
76
|
/** Alias for the result. Auto-generated by URL parser if omitted. */
|
|
77
|
-
$as?:
|
|
77
|
+
$as?: Alias;
|
|
78
78
|
}
|
|
79
79
|
/**
|
|
80
80
|
* Projection definition.
|
|
@@ -92,6 +92,8 @@ interface UniqueryControls<T = Record<string, unknown>, Nav extends Record<strin
|
|
|
92
92
|
$select?: SelectExpr<T>;
|
|
93
93
|
/** Fields to group by for aggregate queries. */
|
|
94
94
|
$groupBy?: (keyof T & string)[];
|
|
95
|
+
/** Post-aggregation filter. Operates on aggregate aliases and dimension fields. */
|
|
96
|
+
$having?: FilterExpr;
|
|
95
97
|
/** Relations to populate alongside the query. */
|
|
96
98
|
$with?: TypedWithRelation<Nav>[];
|
|
97
99
|
/** Pass-through for unknown $-prefixed keywords. */
|
|
@@ -145,9 +147,46 @@ type WithRelation = {
|
|
|
145
147
|
* Insight operator includes comparison ops, control ops ($-prefixed),
|
|
146
148
|
* and aggregate function names (bare, e.g. 'sum', 'avg').
|
|
147
149
|
*/
|
|
148
|
-
type InsightOp = ComparisonOp | '$select' | '$order' | '$with' | '$groupBy' | AggregateFn | (string & {});
|
|
150
|
+
type InsightOp = ComparisonOp | '$select' | '$order' | '$with' | '$groupBy' | '$having' | AggregateFn | (string & {});
|
|
149
151
|
/** Map of field names to the set of operators used on that field. */
|
|
150
152
|
type UniqueryInsights = Map<string, Set<InsightOp>>;
|
|
153
|
+
/** Aggregate query controls. Separate from UniqueryControls: $groupBy is required, $with is forbidden. */
|
|
154
|
+
interface AggregateControls<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
155
|
+
$groupBy: D[];
|
|
156
|
+
$select?: (D | AggregateExpr<AggregateFn, M | '*'>)[];
|
|
157
|
+
$having?: FilterExpr;
|
|
158
|
+
$sort?: Record<string, 1 | -1>;
|
|
159
|
+
$skip?: number;
|
|
160
|
+
$limit?: number;
|
|
161
|
+
$count?: boolean;
|
|
162
|
+
[key: `$${string}`]: unknown;
|
|
163
|
+
}
|
|
164
|
+
/** Aggregate query — no name (can't nest), no Nav (no $with). */
|
|
165
|
+
interface AggregateQuery<T = Record<string, unknown>, D extends keyof T & string = keyof T & string, M extends keyof T & string = keyof T & string> {
|
|
166
|
+
filter?: FilterExpr<T>;
|
|
167
|
+
controls: AggregateControls<T, D, M>;
|
|
168
|
+
insights?: UniqueryInsights;
|
|
169
|
+
}
|
|
170
|
+
/** Resolve the output alias of an AggregateExpr. Uses $as if provided, otherwise generates {fn}_{field}. */
|
|
171
|
+
type ResolveAlias<A extends AggregateExpr> = A extends {
|
|
172
|
+
$as: infer Alias extends string;
|
|
173
|
+
} ? Alias : A extends {
|
|
174
|
+
$fn: infer Fn extends string;
|
|
175
|
+
$field: infer F extends string;
|
|
176
|
+
} ? `${Fn}_${F}` : string;
|
|
177
|
+
/**
|
|
178
|
+
* Infer the result row type from an aggregate query's $select.
|
|
179
|
+
* Dimension fields preserve their original type from T.
|
|
180
|
+
* Aggregate expressions: min/max preserve original type, others → number.
|
|
181
|
+
*/
|
|
182
|
+
type AggregateResult<T, Select extends readonly (string | AggregateExpr)[]> = {
|
|
183
|
+
[K in Extract<Select[number], string> & keyof T]: T[K];
|
|
184
|
+
} & {
|
|
185
|
+
[A in Extract<Select[number], AggregateExpr> as ResolveAlias<A>]: A extends {
|
|
186
|
+
$fn: 'min' | 'max';
|
|
187
|
+
$field: infer F extends keyof T & string;
|
|
188
|
+
} ? T[F] : number;
|
|
189
|
+
};
|
|
151
190
|
|
|
152
191
|
/**
|
|
153
192
|
* Visitor callbacks for controlling how filter nodes are processed.
|
|
@@ -188,4 +227,4 @@ declare function computeInsights(filter?: FilterExpr, controls?: UniqueryControl
|
|
|
188
227
|
declare function getInsights(query: Uniquery): UniqueryInsights;
|
|
189
228
|
|
|
190
229
|
export { computeInsights, getInsights, isPrimitive, walkFilter };
|
|
191
|
-
export type { AggregateExpr, AggregateFn, ComparisonNode, ComparisonOp, FieldOps, FieldOpsFor, FieldValue, FilterExpr, FilterVisitor, InsightOp, LogicalNode, NavTarget, Primitive, SelectExpr, TypedWithRelation, Uniquery, UniqueryControls, UniqueryInsights, WithRelation };
|
|
230
|
+
export type { AggregateControls, AggregateExpr, AggregateFn, AggregateQuery, AggregateResult, ComparisonNode, ComparisonOp, FieldOps, FieldOpsFor, FieldValue, FilterExpr, FilterVisitor, InsightOp, LogicalNode, NavTarget, Primitive, ResolveAlias, SelectExpr, TypedWithRelation, Uniquery, UniqueryControls, UniqueryInsights, WithRelation };
|
package/dist/index.mjs
CHANGED
|
@@ -70,6 +70,14 @@ function isPrimitive(x) {
|
|
|
70
70
|
else capture(entry.$field, entry.$fn);
|
|
71
71
|
else for (const field of Object.keys(controls.$select)) capture(field, "$select");
|
|
72
72
|
if (controls?.$groupBy) for (const field of controls.$groupBy) capture(field, "$groupBy");
|
|
73
|
+
if (controls?.$having) walkFilter(controls.$having, {
|
|
74
|
+
comparison(field) {
|
|
75
|
+
capture(field, "$having");
|
|
76
|
+
},
|
|
77
|
+
and() {},
|
|
78
|
+
or() {},
|
|
79
|
+
not() {}
|
|
80
|
+
});
|
|
73
81
|
if (controls?.$sort) for (const field of Object.keys(controls.$sort)) capture(field, "$order");
|
|
74
82
|
if (controls?.$with) for (const entry of controls.$with) {
|
|
75
83
|
if (typeof entry === "string") {
|