@hypequery/datasets 0.13.6 → 0.15.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/README.md +10 -0
- package/dist/agent-catalog.d.ts +97 -0
- package/dist/agent-catalog.d.ts.map +1 -0
- package/dist/agent-catalog.js +79 -0
- package/dist/api.type-test.js +19 -1
- package/dist/catalog.d.ts +17 -6
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +18 -1
- package/dist/contract.d.ts +11 -7
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +22 -1
- package/dist/dataset-query.js +1 -1
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +19 -2
- package/dist/executor.d.ts +6 -0
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +55 -23
- package/dist/field.d.ts.map +1 -1
- package/dist/field.js +2 -0
- package/dist/index.d.ts +12 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +9 -0
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +4 -0
- package/dist/portable-execution-errors.d.ts +49 -0
- package/dist/portable-execution-errors.d.ts.map +1 -0
- package/dist/portable-execution-errors.js +52 -0
- package/dist/portable-executor.d.ts +61 -0
- package/dist/portable-executor.d.ts.map +1 -0
- package/dist/portable-executor.js +90 -0
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +26 -97
- package/dist/protocol-rehydrate.d.ts +56 -0
- package/dist/protocol-rehydrate.d.ts.map +1 -0
- package/dist/protocol-rehydrate.js +264 -0
- package/dist/publishing/features/dataset-publication.d.ts +10 -0
- package/dist/publishing/features/dataset-publication.d.ts.map +1 -0
- package/dist/publishing/features/dataset-publication.js +34 -0
- package/dist/publishing/node.d.ts +16 -0
- package/dist/publishing/node.d.ts.map +1 -0
- package/dist/publishing/node.js +35 -0
- package/dist/publishing/publisher.d.ts +17 -0
- package/dist/publishing/publisher.d.ts.map +1 -0
- package/dist/publishing/publisher.js +32 -0
- package/dist/publishing/types.d.ts +31 -0
- package/dist/publishing/types.d.ts.map +1 -0
- package/dist/publishing/types.js +1 -0
- package/dist/query-builder-protocol.d.ts +6 -2
- package/dist/query-builder-protocol.d.ts.map +1 -1
- package/dist/semantic-plan.d.ts +3 -1
- package/dist/semantic-plan.d.ts.map +1 -1
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +4 -0
- package/dist/semantic-query-schema.d.ts +60 -0
- package/dist/semantic-query-schema.d.ts.map +1 -0
- package/dist/semantic-query-schema.js +223 -0
- package/dist/sql-utils.d.ts +36 -0
- package/dist/sql-utils.d.ts.map +1 -1
- package/dist/sql-utils.js +76 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +25 -267
- package/dist/types.d.ts +98 -8
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +78 -0
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -0
- package/dist/utils/agent-catalog-projection.js +179 -0
- package/dist/utils/dataset-cache-policy.d.ts +34 -0
- package/dist/utils/dataset-cache-policy.d.ts.map +1 -0
- package/dist/utils/dataset-cache-policy.js +50 -0
- package/dist/utils/dataset-contract.d.ts +2 -2
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +3 -1
- package/dist/utils/dataset-definition-validation.d.ts +35 -0
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -0
- package/dist/utils/dataset-definition-validation.js +215 -0
- package/dist/utils/dataset-metric-ref.d.ts +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-metric-ref.js +5 -3
- package/dist/utils/metric-alias.d.ts +4 -0
- package/dist/utils/metric-alias.d.ts.map +1 -0
- package/dist/utils/metric-alias.js +28 -0
- package/dist/utils/portable-execution-deadline.d.ts +4 -0
- package/dist/utils/portable-execution-deadline.d.ts.map +1 -0
- package/dist/utils/portable-execution-deadline.js +27 -0
- package/dist/utils/portable-result-budget.d.ts +12 -0
- package/dist/utils/portable-result-budget.d.ts.map +1 -0
- package/dist/utils/portable-result-budget.js +21 -0
- package/dist/utils/portable-semantic-query.d.ts +5 -0
- package/dist/utils/portable-semantic-query.d.ts.map +1 -0
- package/dist/utils/portable-semantic-query.js +36 -0
- package/dist/utils/protocol-metric-capabilities.d.ts +7 -0
- package/dist/utils/protocol-metric-capabilities.d.ts.map +1 -0
- package/dist/utils/protocol-metric-capabilities.js +57 -0
- package/dist/utils/protocol-metric-expressions.d.ts +17 -0
- package/dist/utils/protocol-metric-expressions.d.ts.map +1 -0
- package/dist/utils/protocol-metric-expressions.js +118 -0
- package/dist/utils/protocol-rehydrate-derivation.d.ts +27 -0
- package/dist/utils/protocol-rehydrate-derivation.d.ts.map +1 -0
- package/dist/utils/protocol-rehydrate-derivation.js +104 -0
- package/dist/utils/protocol-rehydrate-filters.d.ts +10 -0
- package/dist/utils/protocol-rehydrate-filters.d.ts.map +1 -0
- package/dist/utils/protocol-rehydrate-filters.js +36 -0
- package/dist/utils/protocol-semantic-metadata.d.ts +5 -0
- package/dist/utils/protocol-semantic-metadata.d.ts.map +1 -0
- package/dist/utils/protocol-semantic-metadata.js +5 -0
- package/dist/utils/published-name.d.ts +3 -0
- package/dist/utils/published-name.d.ts.map +1 -0
- package/dist/utils/published-name.js +7 -0
- package/dist/utils/published-relationships.d.ts +34 -0
- package/dist/utils/published-relationships.d.ts.map +1 -0
- package/dist/utils/published-relationships.js +35 -0
- package/dist/utils/relationship-fields.d.ts +7 -0
- package/dist/utils/relationship-fields.d.ts.map +1 -1
- package/dist/utils/relationship-fields.js +13 -1
- package/dist/utils/result-limits.d.ts +34 -0
- package/dist/utils/result-limits.d.ts.map +1 -0
- package/dist/utils/result-limits.js +40 -0
- package/dist/utils/semantic-metadata-validation.d.ts +4 -0
- package/dist/utils/semantic-metadata-validation.d.ts.map +1 -0
- package/dist/utils/semantic-metadata-validation.js +103 -0
- package/dist/utils/semantic-metadata.d.ts +4 -0
- package/dist/utils/semantic-metadata.d.ts.map +1 -0
- package/dist/utils/semantic-metadata.js +13 -0
- package/dist/utils/semantic-tool.d.ts +5 -0
- package/dist/utils/semantic-tool.d.ts.map +1 -0
- package/dist/utils/semantic-tool.js +29 -0
- package/package.json +5 -4
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, RelationshipDefinition } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Resolves a published alias back to the materialized instance registered under
|
|
4
|
+
* it. Deliberately a lookup rather than a value: relationships are rewired
|
|
5
|
+
* while the registry is still being built, and datasets may reference each
|
|
6
|
+
* other, so a target is resolved on call rather than at rewiring time.
|
|
7
|
+
*/
|
|
8
|
+
export type PublishedLookup = (alias: string) => AnyDatasetInstance | undefined;
|
|
9
|
+
/**
|
|
10
|
+
* Maps each published dataset's defined name to the alias it was published
|
|
11
|
+
* under. Keyed by name because that is the uniqueness the publisher enforces —
|
|
12
|
+
* it rejects a second entry for the same dataset name — so a copied instance
|
|
13
|
+
* still resolves to the right alias.
|
|
14
|
+
*/
|
|
15
|
+
export declare function publishedAliasesByName(entries: readonly {
|
|
16
|
+
readonly alias: string;
|
|
17
|
+
readonly dataset: AnyDatasetInstance;
|
|
18
|
+
}[]): ReadonlyMap<string, string>;
|
|
19
|
+
/**
|
|
20
|
+
* Rewires a dataset's relationships onto the aliases their targets were
|
|
21
|
+
* published under.
|
|
22
|
+
*
|
|
23
|
+
* Publishing only renames the copied dataset, so without this a relationship's
|
|
24
|
+
* `target()` closure still returns the original instance — a dataset published
|
|
25
|
+
* as `accounts` would still be advertised as `customers` by everything that
|
|
26
|
+
* relates to it, giving agents a target absent from the registry and a
|
|
27
|
+
* reference the deployment contract validator rejects.
|
|
28
|
+
*
|
|
29
|
+
* A target that was not published is left untouched: the join still resolves
|
|
30
|
+
* through the original instance, which is how an unpublished target already
|
|
31
|
+
* behaves in a hand-built registry.
|
|
32
|
+
*/
|
|
33
|
+
export declare function rewirePublishedRelationships(relationships: Readonly<Record<string, RelationshipDefinition>>, aliasesByName: ReadonlyMap<string, string>, lookup: PublishedLookup): Record<string, RelationshipDefinition>;
|
|
34
|
+
//# sourceMappingURL=published-relationships.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"published-relationships.d.ts","sourceRoot":"","sources":["../../src/utils/published-relationships.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAE9E;;;;;GAKG;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,KAAK,EAAE,MAAM,KAAK,kBAAkB,GAAG,SAAS,CAAC;AAEhF;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,SAAS;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;CAAE,EAAE,GACnF,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAE7B;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,4BAA4B,CAC1C,aAAa,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,EAC/D,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1C,MAAM,EAAE,eAAe,GACtB,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAUxC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Maps each published dataset's defined name to the alias it was published
|
|
3
|
+
* under. Keyed by name because that is the uniqueness the publisher enforces —
|
|
4
|
+
* it rejects a second entry for the same dataset name — so a copied instance
|
|
5
|
+
* still resolves to the right alias.
|
|
6
|
+
*/
|
|
7
|
+
export function publishedAliasesByName(entries) {
|
|
8
|
+
return new Map(entries.map(entry => [entry.dataset.name, entry.alias]));
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Rewires a dataset's relationships onto the aliases their targets were
|
|
12
|
+
* published under.
|
|
13
|
+
*
|
|
14
|
+
* Publishing only renames the copied dataset, so without this a relationship's
|
|
15
|
+
* `target()` closure still returns the original instance — a dataset published
|
|
16
|
+
* as `accounts` would still be advertised as `customers` by everything that
|
|
17
|
+
* relates to it, giving agents a target absent from the registry and a
|
|
18
|
+
* reference the deployment contract validator rejects.
|
|
19
|
+
*
|
|
20
|
+
* A target that was not published is left untouched: the join still resolves
|
|
21
|
+
* through the original instance, which is how an unpublished target already
|
|
22
|
+
* behaves in a hand-built registry.
|
|
23
|
+
*/
|
|
24
|
+
export function rewirePublishedRelationships(relationships, aliasesByName, lookup) {
|
|
25
|
+
const rewired = Object.entries(relationships).map(([name, relationship]) => {
|
|
26
|
+
const alias = aliasesByName.get(relationship.target().name);
|
|
27
|
+
if (alias === undefined)
|
|
28
|
+
return [name, relationship];
|
|
29
|
+
return [name, Object.freeze({
|
|
30
|
+
...relationship,
|
|
31
|
+
target: () => lookup(alias) ?? relationship.target(),
|
|
32
|
+
})];
|
|
33
|
+
});
|
|
34
|
+
return Object.freeze(Object.fromEntries(rewired));
|
|
35
|
+
}
|
|
@@ -15,6 +15,13 @@ import type { AnyDatasetInstance, DimensionDefinition, RelationshipDefinition }
|
|
|
15
15
|
* generated input schemas all advertise exactly this list.
|
|
16
16
|
*/
|
|
17
17
|
export declare function listQueryableRelationshipFields(name: string, relationship: RelationshipDefinition): string[];
|
|
18
|
+
/**
|
|
19
|
+
* The subset of {@link listQueryableRelationshipFields} that may be used as a
|
|
20
|
+
* grouping key, honouring `groupable: false` on the target dimension. The rest
|
|
21
|
+
* stay queryable as filters, which is why this is a separate list rather than a
|
|
22
|
+
* narrowing of the one above.
|
|
23
|
+
*/
|
|
24
|
+
export declare function listGroupableRelationshipFields(name: string, relationship: RelationshipDefinition): string[];
|
|
18
25
|
export interface ParsedQualifiedField {
|
|
19
26
|
/** The relationship name (prefix before the first dot). */
|
|
20
27
|
relationship: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"relationship-fields.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,
|
|
1
|
+
{"version":3,"file":"relationship-fields.d.ts","sourceRoot":"","sources":["../../src/utils/relationship-fields.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,mBAAmB,EACnB,sBAAsB,EACvB,MAAM,aAAa,CAAC;AAErB;;;;;;GAMG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAED;;;;;GAKG;AACH,wBAAgB,+BAA+B,CAC7C,IAAI,EAAE,MAAM,EACZ,YAAY,EAAE,sBAAsB,GACnC,MAAM,EAAE,CAEV;AAgBD,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,YAAY,EAAE,MAAM,CAAC;IACrB,6EAA6E;IAC7E,KAAK,EAAE,MAAM,CAAC;CACf;AAED,yEAAyE;AACzE,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtD;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,oBAAoB,GAAG,IAAI,CAM7E;AAED,MAAM,WAAW,sBAAsB;IACrC,8EAA8E;IAC9E,gBAAgB,EAAE,MAAM,CAAC;IACzB,YAAY,EAAE,sBAAsB,CAAC;IACrC,4CAA4C;IAC5C,MAAM,EAAE,kBAAkB,CAAC;IAC3B,+DAA+D;IAC/D,mBAAmB,EAAE,MAAM,CAAC;IAC5B,eAAe,EAAE,mBAAmB,CAAC;IACrC,oDAAoD;IACpD,YAAY,EAAE,MAAM,CAAC;IACrB,yEAAyE;IACzE,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,MAAM,wBAAwB,GAChC;IAAE,QAAQ,EAAE,sBAAsB,CAAC;IAAC,KAAK,CAAC,EAAE,SAAS,CAAA;CAAE,GACvD;IAAE,QAAQ,CAAC,EAAE,SAAS,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAE5C;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,kBAAkB,EACtB,IAAI,EAAE,MAAM,GACX,wBAAwB,GAAG,IAAI,CAuDjC"}
|
|
@@ -14,12 +14,24 @@
|
|
|
14
14
|
* generated input schemas all advertise exactly this list.
|
|
15
15
|
*/
|
|
16
16
|
export function listQueryableRelationshipFields(name, relationship) {
|
|
17
|
+
return relationshipFields(name, relationship, () => true);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* The subset of {@link listQueryableRelationshipFields} that may be used as a
|
|
21
|
+
* grouping key, honouring `groupable: false` on the target dimension. The rest
|
|
22
|
+
* stay queryable as filters, which is why this is a separate list rather than a
|
|
23
|
+
* narrowing of the one above.
|
|
24
|
+
*/
|
|
25
|
+
export function listGroupableRelationshipFields(name, relationship) {
|
|
26
|
+
return relationshipFields(name, relationship, dimension => dimension.groupable !== false);
|
|
27
|
+
}
|
|
28
|
+
function relationshipFields(name, relationship, include) {
|
|
17
29
|
if (relationship.kind === 'hasMany') {
|
|
18
30
|
return [];
|
|
19
31
|
}
|
|
20
32
|
const target = relationship.target();
|
|
21
33
|
return Object.entries(target?.dimensions ?? {})
|
|
22
|
-
.filter(([, dimension]) => !dimension.sql)
|
|
34
|
+
.filter(([, dimension]) => !dimension.sql && include(dimension))
|
|
23
35
|
.map(([field]) => `${name}.${field}`);
|
|
24
36
|
}
|
|
25
37
|
/** True when a field name is relationship-qualified (contains a dot). */
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DatasetLimits, MetricResultMeta } from '../types.js';
|
|
2
|
+
export type ResultLimitMeta = NonNullable<MetricResultMeta['resultLimit']>;
|
|
3
|
+
export interface ResolvedResultLimit {
|
|
4
|
+
/** The limit to execute with; `undefined` leaves the query unbounded. */
|
|
5
|
+
limit?: number;
|
|
6
|
+
/** Meta to surface, present only when the declared ceiling supplied the limit. */
|
|
7
|
+
meta?: ResultLimitMeta;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Applies a dataset's declared `maxResultSize` to a query that set no limit.
|
|
11
|
+
*
|
|
12
|
+
* Query validation already rejects a limit *above* the ceiling, so the case
|
|
13
|
+
* left open is the one with no limit at all: `query.limit != null` guards both
|
|
14
|
+
* validators, so an unbounded query has always skipped the ceiling entirely and
|
|
15
|
+
* streamed whatever the table held. A ceiling that only binds callers who
|
|
16
|
+
* happened to name a limit is not a ceiling.
|
|
17
|
+
*
|
|
18
|
+
* Bounding is reported, never silent. A caller who asked for everything and
|
|
19
|
+
* received 1,000 rows cannot otherwise distinguish a bounded answer from a
|
|
20
|
+
* complete one, and 1,000 rows is a plausible-looking number.
|
|
21
|
+
*/
|
|
22
|
+
export declare function resolveResultLimit(requested: number | undefined, limits: DatasetLimits | undefined): ResolvedResultLimit;
|
|
23
|
+
/**
|
|
24
|
+
* Merges result-limit meta onto a pending result.
|
|
25
|
+
*
|
|
26
|
+
* Takes the promise rather than the resolved value so the caller stays
|
|
27
|
+
* synchronous: query validation throws before execution begins, and callers
|
|
28
|
+
* (and their tests) rely on that being a synchronous throw rather than a
|
|
29
|
+
* rejected promise.
|
|
30
|
+
*/
|
|
31
|
+
export declare function withResultLimit<T extends {
|
|
32
|
+
meta?: MetricResultMeta;
|
|
33
|
+
}>(result: Promise<T>, meta: ResultLimitMeta | undefined): Promise<T>;
|
|
34
|
+
//# sourceMappingURL=result-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"result-limits.d.ts","sourceRoot":"","sources":["../../src/utils/result-limits.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAEnE,MAAM,MAAM,eAAe,GAAG,WAAW,CAAC,gBAAgB,CAAC,aAAa,CAAC,CAAC,CAAC;AAE3E,MAAM,WAAW,mBAAmB;IAClC,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,kFAAkF;IAClF,IAAI,CAAC,EAAE,eAAe,CAAC;CACxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,kBAAkB,CAChC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,MAAM,EAAE,aAAa,GAAG,SAAS,GAChC,mBAAmB,CAWrB;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAAC,CAAC,SAAS;IAAE,IAAI,CAAC,EAAE,gBAAgB,CAAA;CAAE,EACnE,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC,EAClB,IAAI,EAAE,eAAe,GAAG,SAAS,GAChC,OAAO,CAAC,CAAC,CAAC,CAQZ"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Applies a dataset's declared `maxResultSize` to a query that set no limit.
|
|
3
|
+
*
|
|
4
|
+
* Query validation already rejects a limit *above* the ceiling, so the case
|
|
5
|
+
* left open is the one with no limit at all: `query.limit != null` guards both
|
|
6
|
+
* validators, so an unbounded query has always skipped the ceiling entirely and
|
|
7
|
+
* streamed whatever the table held. A ceiling that only binds callers who
|
|
8
|
+
* happened to name a limit is not a ceiling.
|
|
9
|
+
*
|
|
10
|
+
* Bounding is reported, never silent. A caller who asked for everything and
|
|
11
|
+
* received 1,000 rows cannot otherwise distinguish a bounded answer from a
|
|
12
|
+
* complete one, and 1,000 rows is a plausible-looking number.
|
|
13
|
+
*/
|
|
14
|
+
export function resolveResultLimit(requested, limits) {
|
|
15
|
+
const maxResultSize = limits?.maxResultSize;
|
|
16
|
+
if (maxResultSize === undefined || requested !== undefined) {
|
|
17
|
+
return { limit: requested };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
limit: maxResultSize,
|
|
21
|
+
meta: { maxResultSize, applied: maxResultSize },
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Merges result-limit meta onto a pending result.
|
|
26
|
+
*
|
|
27
|
+
* Takes the promise rather than the resolved value so the caller stays
|
|
28
|
+
* synchronous: query validation throws before execution begins, and callers
|
|
29
|
+
* (and their tests) rely on that being a synchronous throw rather than a
|
|
30
|
+
* rejected promise.
|
|
31
|
+
*/
|
|
32
|
+
export function withResultLimit(result, meta) {
|
|
33
|
+
if (meta === undefined) {
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
return result.then((value) => ({
|
|
37
|
+
...value,
|
|
38
|
+
meta: { ...(value.meta ?? {}), resultLimit: { ...meta } },
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { DatasetConfig, DimensionDefinition, MeasureDefinition, RelationshipDefinition, SemanticMetadata } from '../types.js';
|
|
2
|
+
export declare function validateSemanticMetadata(datasetName: string, location: string, metadata: SemanticMetadata): void;
|
|
3
|
+
export declare function validateDatasetAgentMetadata<TDimensions extends Record<string, DimensionDefinition>, TMeasures extends Record<string, MeasureDefinition>, TRelationships extends Record<string, RelationshipDefinition>>(datasetName: string, config: DatasetConfig<TDimensions, TMeasures, TRelationships>): void;
|
|
4
|
+
//# sourceMappingURL=semantic-metadata-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-metadata-validation.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata-validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,mBAAmB,EACnB,iBAAiB,EACjB,sBAAsB,EACtB,gBAAgB,EACjB,MAAM,aAAa,CAAC;AAoBrB,wBAAgB,wBAAwB,CACtC,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,gBAAgB,GACzB,IAAI,CA8BN;AAED,wBAAgB,4BAA4B,CAC1C,WAAW,SAAS,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,EACvD,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnD,cAAc,SAAS,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,EAE7D,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,aAAa,CAAC,WAAW,EAAE,SAAS,EAAE,cAAc,CAAC,GAC5D,IAAI,CAuDN"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
const MAX_METADATA_ITEMS = 100;
|
|
2
|
+
const MAX_METADATA_TEXT_BYTES = 4_096;
|
|
3
|
+
const SENSITIVITIES = new Set(['public', 'internal', 'confidential', 'restricted']);
|
|
4
|
+
const TIME_GRAINS = new Set(['day', 'week', 'month', 'quarter', 'year']);
|
|
5
|
+
const textEncoder = new TextEncoder();
|
|
6
|
+
function fail(datasetName, location, message) {
|
|
7
|
+
throw new Error(`Invalid dataset "${datasetName}": ${location} ${message}`);
|
|
8
|
+
}
|
|
9
|
+
function validateText(datasetName, location, value) {
|
|
10
|
+
if (typeof value !== 'string')
|
|
11
|
+
fail(datasetName, location, 'must be a string.');
|
|
12
|
+
if (value.trim().length === 0)
|
|
13
|
+
fail(datasetName, location, 'must not be empty.');
|
|
14
|
+
if (textEncoder.encode(value).byteLength > MAX_METADATA_TEXT_BYTES) {
|
|
15
|
+
fail(datasetName, location, `must not exceed ${MAX_METADATA_TEXT_BYTES} UTF-8 bytes.`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function validateSemanticMetadata(datasetName, location, metadata) {
|
|
19
|
+
for (const key of ['examples', 'synonyms']) {
|
|
20
|
+
const values = metadata[key];
|
|
21
|
+
if (values === undefined)
|
|
22
|
+
continue;
|
|
23
|
+
if (!Array.isArray(values) || values.length > MAX_METADATA_ITEMS) {
|
|
24
|
+
fail(datasetName, `${location}.${key}`, `must contain at most ${MAX_METADATA_ITEMS} strings.`);
|
|
25
|
+
}
|
|
26
|
+
if (new Set(values).size !== values.length) {
|
|
27
|
+
fail(datasetName, `${location}.${key}`, 'must not contain duplicates.');
|
|
28
|
+
}
|
|
29
|
+
for (const [index, value] of values.entries()) {
|
|
30
|
+
if (typeof value !== 'string') {
|
|
31
|
+
fail(datasetName, `${location}.${key}[${index}]`, 'must be a string.');
|
|
32
|
+
}
|
|
33
|
+
validateText(datasetName, `${location}.${key}[${index}]`, value);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
for (const key of ['format', 'unit', 'timezone']) {
|
|
37
|
+
const value = metadata[key];
|
|
38
|
+
if (value !== undefined)
|
|
39
|
+
validateText(datasetName, `${location}.${key}`, value);
|
|
40
|
+
}
|
|
41
|
+
if (metadata.currency !== undefined) {
|
|
42
|
+
if (typeof metadata.currency !== 'string' || !/^[A-Z]{3}$/.test(metadata.currency)) {
|
|
43
|
+
fail(datasetName, `${location}.currency`, 'must be a three-letter uppercase currency code.');
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (metadata.sensitivity !== undefined
|
|
47
|
+
&& (typeof metadata.sensitivity !== 'string' || !SENSITIVITIES.has(metadata.sensitivity))) {
|
|
48
|
+
fail(datasetName, `${location}.sensitivity`, 'is not a supported sensitivity classification.');
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function validateDatasetAgentMetadata(datasetName, config) {
|
|
52
|
+
validateSemanticMetadata(datasetName, 'metadata', config);
|
|
53
|
+
if (config.description !== undefined)
|
|
54
|
+
validateText(datasetName, 'description', config.description);
|
|
55
|
+
if (config.owner !== undefined)
|
|
56
|
+
validateText(datasetName, 'owner', config.owner);
|
|
57
|
+
if (config.freshness !== undefined) {
|
|
58
|
+
if (typeof config.freshness !== 'object' || config.freshness === null
|
|
59
|
+
|| !Number.isSafeInteger(config.freshness.maxAgeSeconds)
|
|
60
|
+
|| config.freshness.maxAgeSeconds < 1) {
|
|
61
|
+
fail(datasetName, 'freshness.maxAgeSeconds', 'must be a positive safe integer.');
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (config.defaults !== undefined) {
|
|
65
|
+
if (typeof config.defaults !== 'object' || config.defaults === null) {
|
|
66
|
+
fail(datasetName, 'defaults', 'must be an object.');
|
|
67
|
+
}
|
|
68
|
+
if (config.defaults.dimensions !== undefined && !Array.isArray(config.defaults.dimensions)) {
|
|
69
|
+
fail(datasetName, 'defaults.dimensions', 'must be an array.');
|
|
70
|
+
}
|
|
71
|
+
const dimensions = config.defaults.dimensions ?? [];
|
|
72
|
+
if (dimensions.length > MAX_METADATA_ITEMS) {
|
|
73
|
+
fail(datasetName, 'defaults.dimensions', `must contain at most ${MAX_METADATA_ITEMS} dimensions.`);
|
|
74
|
+
}
|
|
75
|
+
if (new Set(dimensions).size !== dimensions.length) {
|
|
76
|
+
fail(datasetName, 'defaults.dimensions', 'must not contain duplicates.');
|
|
77
|
+
}
|
|
78
|
+
for (const dimension of dimensions) {
|
|
79
|
+
const definition = config.dimensions[dimension];
|
|
80
|
+
if (!definition || definition.groupable === false) {
|
|
81
|
+
fail(datasetName, 'defaults.dimensions', `references non-groupable dimension "${dimension}".`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (config.defaults.timeGrain !== undefined && config.timeKey === undefined) {
|
|
85
|
+
fail(datasetName, 'defaults.timeGrain', 'requires the dataset to define timeKey.');
|
|
86
|
+
}
|
|
87
|
+
if (config.defaults.timeGrain !== undefined && !TIME_GRAINS.has(config.defaults.timeGrain)) {
|
|
88
|
+
fail(datasetName, 'defaults.timeGrain', 'is not a supported time grain.');
|
|
89
|
+
}
|
|
90
|
+
if (dimensions.length === 0 && config.defaults.timeGrain === undefined) {
|
|
91
|
+
fail(datasetName, 'defaults', 'must define dimensions or timeGrain.');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
for (const [name, dimension] of Object.entries(config.dimensions)) {
|
|
95
|
+
validateSemanticMetadata(datasetName, `dimensions.${name}`, dimension);
|
|
96
|
+
}
|
|
97
|
+
for (const [name, measure] of Object.entries(config.measures ?? {})) {
|
|
98
|
+
validateSemanticMetadata(datasetName, `measures.${name}`, measure);
|
|
99
|
+
}
|
|
100
|
+
for (const [name, filter] of Object.entries(config.filters ?? {})) {
|
|
101
|
+
validateSemanticMetadata(datasetName, `filters.${name}`, filter);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SemanticMetadata } from '../types.js';
|
|
2
|
+
/** Copy agent metadata without carrying unrelated config fields into public contracts. */
|
|
3
|
+
export declare function snapshotSemanticMetadata(metadata: SemanticMetadata): SemanticMetadata;
|
|
4
|
+
//# sourceMappingURL=semantic-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,0FAA0F;AAC1F,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,gBAAgB,CAUrF"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { uniqueSorted } from './canonical-json.js';
|
|
2
|
+
/** Copy agent metadata without carrying unrelated config fields into public contracts. */
|
|
3
|
+
export function snapshotSemanticMetadata(metadata) {
|
|
4
|
+
return {
|
|
5
|
+
...(metadata.examples !== undefined ? { examples: uniqueSorted(metadata.examples) } : {}),
|
|
6
|
+
...(metadata.synonyms !== undefined ? { synonyms: uniqueSorted(metadata.synonyms) } : {}),
|
|
7
|
+
...(metadata.format !== undefined ? { format: metadata.format } : {}),
|
|
8
|
+
...(metadata.unit !== undefined ? { unit: metadata.unit } : {}),
|
|
9
|
+
...(metadata.currency !== undefined ? { currency: metadata.currency } : {}),
|
|
10
|
+
...(metadata.timezone !== undefined ? { timezone: metadata.timezone } : {}),
|
|
11
|
+
...(metadata.sensitivity !== undefined ? { sensitivity: metadata.sensitivity } : {}),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ZodTypeAny } from 'zod';
|
|
2
|
+
export declare function semanticToolNamePart(name: string): string;
|
|
3
|
+
export declare function parseCanonicalToolInput(schema: ZodTypeAny, input: Record<string, unknown>, label: string): Record<string, unknown>;
|
|
4
|
+
export declare function redactSemanticToolSql(result: unknown, includeSql: boolean): unknown;
|
|
5
|
+
//# sourceMappingURL=semantic-tool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"semantic-tool.d.ts","sourceRoot":"","sources":["../../src/utils/semantic-tool.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,KAAK,CAAC;AAEtC,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAGzD;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,UAAU,EAClB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC9B,KAAK,EAAE,MAAM,GACZ,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAUzB;AAED,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,OAAO,GAAG,OAAO,CAenF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export function semanticToolNamePart(name) {
|
|
2
|
+
const normalized = name.replace(/[^A-Za-z0-9_-]/g, '_');
|
|
3
|
+
return /^[A-Za-z_]/.test(normalized) ? normalized : `_${normalized}`;
|
|
4
|
+
}
|
|
5
|
+
export function parseCanonicalToolInput(schema, input, label) {
|
|
6
|
+
const result = schema.safeParse(input);
|
|
7
|
+
if (!result.success) {
|
|
8
|
+
const issues = result.error.issues.map((issue) => {
|
|
9
|
+
const path = issue.path.length > 0 ? issue.path.join('.') : 'input';
|
|
10
|
+
return `${path}: ${issue.message}`;
|
|
11
|
+
}).join('; ');
|
|
12
|
+
throw new Error(`Invalid ${label}: ${issues}`);
|
|
13
|
+
}
|
|
14
|
+
return result.data;
|
|
15
|
+
}
|
|
16
|
+
export function redactSemanticToolSql(result, includeSql) {
|
|
17
|
+
if (includeSql || !result || typeof result !== 'object') {
|
|
18
|
+
return result;
|
|
19
|
+
}
|
|
20
|
+
const resultObject = result;
|
|
21
|
+
if (!resultObject.meta || typeof resultObject.meta !== 'object' || !('sql' in resultObject.meta)) {
|
|
22
|
+
return result;
|
|
23
|
+
}
|
|
24
|
+
const { sql: _sql, ...meta } = resultObject.meta;
|
|
25
|
+
return {
|
|
26
|
+
...resultObject,
|
|
27
|
+
meta,
|
|
28
|
+
};
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/datasets",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.15.0",
|
|
4
4
|
"description": "Code-first TypeScript semantic layer for ClickHouse datasets, metrics, multi-tenancy, and AI agents",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"semantic-layer",
|
|
@@ -42,9 +42,10 @@
|
|
|
42
42
|
"dist"
|
|
43
43
|
],
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@hypequery/protocol": "^0.
|
|
45
|
+
"@hypequery/protocol": "^0.13.0",
|
|
46
46
|
"@noble/hashes": "^1.8.0",
|
|
47
|
-
"zod": "^3.22.4"
|
|
47
|
+
"zod": "^3.22.4",
|
|
48
|
+
"zod-to-json-schema": "^3.23.5"
|
|
48
49
|
},
|
|
49
50
|
"peerDependencies": {
|
|
50
51
|
"zod": "^3.22.4"
|
|
@@ -59,7 +60,7 @@
|
|
|
59
60
|
"typescript": "^5.7.3",
|
|
60
61
|
"@vitest/coverage-v8": "^3.2.6",
|
|
61
62
|
"vitest": "^3.2.6",
|
|
62
|
-
"@hypequery/protocol-conformance": "^0.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.11.1"
|
|
63
64
|
},
|
|
64
65
|
"repository": {
|
|
65
66
|
"type": "git",
|