@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
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dataset-metric-ref.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-metric-ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEnB,iBAAiB,EACjB,SAAS,EACT,sBAAsB,
|
|
1
|
+
{"version":3,"file":"dataset-metric-ref.d.ts","sourceRoot":"","sources":["../../src/utils/dataset-metric-ref.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EAEnB,iBAAiB,EACjB,SAAS,EACT,sBAAsB,EACtB,gBAAgB,EAEjB,MAAM,aAAa,CAAC;AAIrB,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,mBAAmB,CAAC,CAAC;AACzD,KAAK,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC;AACrD,KAAK,gBAAgB,GAAG,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC;AAE/D,wBAAgB,qBAAqB,CACnC,SAAS,SAAS,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,EACnD,YAAY,SAAS,MAAM,EAE3B,MAAM,EAAE,gBAAgB,CAAC,SAAS,CAAC,GAAG,mBAAmB,CAAC,YAAY,CAAC,GACtE,MAAM,IAAI,mBAAmB,CAAC,YAAY,CAAC,CAE7C;AAED,wBAAgB,eAAe,CAC7B,YAAY,SAAS,MAAM,EAC3B,WAAW,SAAS,MAAM,EAC1B,KAAK,SAAS,eAAe,GAAG,iBAAiB,CAAC,YAAY,CAAC,EAC/D,QAAQ,SAAS,eAAe,CAAC,aAAa,EAAE,WAAW,EAAE,gBAAgB,EAAE,YAAY,CAAC,EAE5F,EAAE,EAAE,QAAQ,EACZ,IAAI,EAAE,WAAW,EACjB,IAAI,EAAE,KAAK,EACX,KAAK,CAAC,EAAE,MAAM,EACd,WAAW,CAAC,EAAE,MAAM,EACpB,QAAQ,GAAE,gBAAqB,GAC9B,SAAS,CAAC,YAAY,EAAE,WAAW,EAAE,KAAK,EAAE,QAAQ,CAAC,CAkCvD;AAED,wBAAgB,uBAAuB,CAAC,YAAY,SAAS,MAAM,EACjE,MAAM,EAAE,mBAAmB,CAAC,YAAY,CAAC,GACxC,iBAAiB,CAAC,YAAY,CAAC,CAMjC;AAED,YAAY,EAAE,aAAa,EAAE,gBAAgB,EAAE,CAAC"}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { buildMetricContract } from './dataset-contract.js';
|
|
2
|
+
import { snapshotSemanticMetadata } from './semantic-metadata.js';
|
|
2
3
|
export function isDerivedMetricConfig(config) {
|
|
3
4
|
return 'uses' in config && 'formula' in config;
|
|
4
5
|
}
|
|
5
|
-
export function createMetricRef(ds, name, spec, label, description) {
|
|
6
|
+
export function createMetricRef(ds, name, spec, label, description, metadata = {}) {
|
|
6
7
|
const ref = {
|
|
7
8
|
__type: 'metric_ref',
|
|
8
9
|
datasetName: ds.name,
|
|
@@ -10,6 +11,7 @@ export function createMetricRef(ds, name, spec, label, description) {
|
|
|
10
11
|
spec,
|
|
11
12
|
label,
|
|
12
13
|
description,
|
|
14
|
+
...snapshotSemanticMetadata(metadata),
|
|
13
15
|
dataset: ds,
|
|
14
16
|
by(grain) {
|
|
15
17
|
if (!ds.timeKey) {
|
|
@@ -20,12 +22,12 @@ export function createMetricRef(ds, name, spec, label, description) {
|
|
|
20
22
|
metric: ref,
|
|
21
23
|
grain,
|
|
22
24
|
contract() {
|
|
23
|
-
return buildMetricContract(name, ds, spec, label, description, grain);
|
|
25
|
+
return buildMetricContract(name, ds, spec, label, description, metadata, grain);
|
|
24
26
|
},
|
|
25
27
|
};
|
|
26
28
|
},
|
|
27
29
|
contract() {
|
|
28
|
-
return buildMetricContract(name, ds, spec, label, description);
|
|
30
|
+
return buildMetricContract(name, ds, spec, label, description, metadata);
|
|
29
31
|
},
|
|
30
32
|
};
|
|
31
33
|
return ref;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { MetricHandle } from '../types.js';
|
|
2
|
+
/** Clone a metric handle with the public name used by a published registry. */
|
|
3
|
+
export declare function aliasMetricHandle(metric: MetricHandle, alias: string, datasetAlias: string): MetricHandle;
|
|
4
|
+
//# sourceMappingURL=metric-alias.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"metric-alias.d.ts","sourceRoot":"","sources":["../../src/utils/metric-alias.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAKV,YAAY,EAEb,MAAM,aAAa,CAAC;AA4BrB,+EAA+E;AAC/E,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,YAAY,EACpB,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GACnB,YAAY,CAYd"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
function aliasMetricRef(metric, alias, datasetAlias) {
|
|
2
|
+
const aliased = {
|
|
3
|
+
...metric,
|
|
4
|
+
datasetName: datasetAlias,
|
|
5
|
+
name: alias,
|
|
6
|
+
by(grain) {
|
|
7
|
+
return aliasMetricHandle(metric.by(grain), alias, datasetAlias);
|
|
8
|
+
},
|
|
9
|
+
contract() {
|
|
10
|
+
return { ...metric.contract(), dataset: datasetAlias, name: alias };
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
return aliased;
|
|
14
|
+
}
|
|
15
|
+
/** Clone a metric handle with the public name used by a published registry. */
|
|
16
|
+
export function aliasMetricHandle(metric, alias, datasetAlias) {
|
|
17
|
+
if (metric.__type === 'metric_ref') {
|
|
18
|
+
return aliasMetricRef(metric, alias, datasetAlias);
|
|
19
|
+
}
|
|
20
|
+
const aliasedMetric = aliasMetricRef(metric.metric, alias, datasetAlias);
|
|
21
|
+
return {
|
|
22
|
+
...metric,
|
|
23
|
+
metric: aliasedMetric,
|
|
24
|
+
contract() {
|
|
25
|
+
return { ...metric.contract(), dataset: datasetAlias, name: alias };
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { PortableSemanticBudget } from '../portable-executor.js';
|
|
2
|
+
/** Abort the underlying request and independently settle the invocation on expiry. */
|
|
3
|
+
export declare function withDeadline<T>(budget: PortableSemanticBudget, signal: AbortSignal | undefined, execute: (signal: AbortSignal) => Promise<T>): Promise<T>;
|
|
4
|
+
//# sourceMappingURL=portable-execution-deadline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-execution-deadline.d.ts","sourceRoot":"","sources":["../../src/utils/portable-execution-deadline.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGtE,sFAAsF;AACtF,wBAAsB,YAAY,CAAC,CAAC,EAClC,MAAM,EAAE,sBAAsB,EAC9B,MAAM,EAAE,WAAW,GAAG,SAAS,EAC/B,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GAC3C,OAAO,CAAC,CAAC,CAAC,CAuBZ"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { PortableExecutionBudgetError } from '../portable-execution-errors.js';
|
|
2
|
+
/** Abort the underlying request and independently settle the invocation on expiry. */
|
|
3
|
+
export async function withDeadline(budget, signal, execute) {
|
|
4
|
+
if (signal?.aborted)
|
|
5
|
+
throw signal.reason ?? new Error('The invocation was cancelled.');
|
|
6
|
+
const controller = new AbortController();
|
|
7
|
+
const abort = () => controller.abort(signal?.reason);
|
|
8
|
+
let rejectAbort = () => { };
|
|
9
|
+
const cancelled = new Promise((_resolve, reject) => {
|
|
10
|
+
rejectAbort = () => reject(controller.signal.reason);
|
|
11
|
+
controller.signal.addEventListener('abort', rejectAbort, { once: true });
|
|
12
|
+
});
|
|
13
|
+
signal?.addEventListener('abort', abort, { once: true });
|
|
14
|
+
const timer = budget.deadlineMs === undefined ? undefined : setTimeout(() => {
|
|
15
|
+
controller.abort(new PortableExecutionBudgetError(`The invocation exceeded its ${String(budget.deadlineMs)}ms deadline.`));
|
|
16
|
+
}, budget.deadlineMs);
|
|
17
|
+
try {
|
|
18
|
+
// Promise.race also observes a late driver rejection after cancellation.
|
|
19
|
+
return await Promise.race([cancelled, execute(controller.signal)]);
|
|
20
|
+
}
|
|
21
|
+
finally {
|
|
22
|
+
if (timer !== undefined)
|
|
23
|
+
clearTimeout(timer);
|
|
24
|
+
signal?.removeEventListener('abort', abort);
|
|
25
|
+
controller.signal.removeEventListener('abort', rejectAbort);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ProtocolSemanticInvocationResult } from '@hypequery/protocol';
|
|
2
|
+
import type { PortableSemanticBudget } from '../portable-executor.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bounds a result by row count and serialized size before it leaves the
|
|
5
|
+
* deployment, so an oversized answer fails rather than being streamed on.
|
|
6
|
+
*
|
|
7
|
+
* The byte budget is measured against the whole record a caller receives, not
|
|
8
|
+
* only its rows, so this agrees with the data plane's own check rather than
|
|
9
|
+
* undercounting by the envelope.
|
|
10
|
+
*/
|
|
11
|
+
export declare function bounded(result: ProtocolSemanticInvocationResult, budget: PortableSemanticBudget): ProtocolSemanticInvocationResult;
|
|
12
|
+
//# sourceMappingURL=portable-result-budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-result-budget.d.ts","sourceRoot":"","sources":["../../src/utils/portable-result-budget.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gCAAgC,EAAE,MAAM,qBAAqB,CAAC;AAC5E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAGtE;;;;;;;GAOG;AACH,wBAAgB,OAAO,CACrB,MAAM,EAAE,gCAAgC,EACxC,MAAM,EAAE,sBAAsB,GAC7B,gCAAgC,CAelC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { PortableExecutionBudgetError } from '../portable-execution-errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Bounds a result by row count and serialized size before it leaves the
|
|
4
|
+
* deployment, so an oversized answer fails rather than being streamed on.
|
|
5
|
+
*
|
|
6
|
+
* The byte budget is measured against the whole record a caller receives, not
|
|
7
|
+
* only its rows, so this agrees with the data plane's own check rather than
|
|
8
|
+
* undercounting by the envelope.
|
|
9
|
+
*/
|
|
10
|
+
export function bounded(result, budget) {
|
|
11
|
+
if (result.data.length > budget.maxRows) {
|
|
12
|
+
throw new PortableExecutionBudgetError(`The result has ${result.data.length} rows; the effective limit is ${budget.maxRows}.`);
|
|
13
|
+
}
|
|
14
|
+
if (budget.maxResponseBytes !== undefined) {
|
|
15
|
+
const bytes = new TextEncoder().encode(JSON.stringify(result)).byteLength;
|
|
16
|
+
if (bytes > budget.maxResponseBytes) {
|
|
17
|
+
throw new PortableExecutionBudgetError(`The result is ${bytes} bytes; the effective limit is ${budget.maxResponseBytes}.`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return result;
|
|
21
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ProtocolSemanticQuery } from '@hypequery/protocol';
|
|
2
|
+
import type { SemanticTenantRuntime } from '../types.js';
|
|
3
|
+
export declare function tenantRuntime(tenant: unknown): SemanticTenantRuntime | undefined;
|
|
4
|
+
export declare function semanticQuery(operation: ProtocolSemanticQuery, maxRows: number): Record<string, unknown>;
|
|
5
|
+
//# sourceMappingURL=portable-semantic-query.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-semantic-query.d.ts","sourceRoot":"","sources":["../../src/utils/portable-semantic-query.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAA+B,qBAAqB,EAAa,MAAM,aAAa,CAAC;AAIjG,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,qBAAqB,GAAG,SAAS,CAKhF;AAWD,wBAAgB,aAAa,CAAC,SAAS,EAAE,qBAAqB,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAqBxG"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PortableExecutionUnsupportedError } from '../portable-execution-errors.js';
|
|
2
|
+
import { rehydrateMeasureFilter } from './protocol-rehydrate-filters.js';
|
|
3
|
+
export function tenantRuntime(tenant) {
|
|
4
|
+
if (tenant === undefined || tenant === null)
|
|
5
|
+
return undefined;
|
|
6
|
+
if (typeof tenant === 'string')
|
|
7
|
+
return tenant;
|
|
8
|
+
if (Array.isArray(tenant))
|
|
9
|
+
return { in: tenant.map(String) };
|
|
10
|
+
return tenant;
|
|
11
|
+
}
|
|
12
|
+
function operationFilters(operation) {
|
|
13
|
+
return (operation.filters ?? []).map((expression, index) => rehydrateMeasureFilter(expression, () => new PortableExecutionUnsupportedError(`Filter ${index} is not a field/operator/value comparison, so it cannot be planned.`)));
|
|
14
|
+
}
|
|
15
|
+
export function semanticQuery(operation, maxRows) {
|
|
16
|
+
const filters = operationFilters(operation);
|
|
17
|
+
const orderBy = (operation.orderBy ?? []).map(entry => ({
|
|
18
|
+
field: String(entry.field),
|
|
19
|
+
direction: entry.direction,
|
|
20
|
+
}));
|
|
21
|
+
return {
|
|
22
|
+
...(operation.dimensions === undefined
|
|
23
|
+
? {}
|
|
24
|
+
: { dimensions: operation.dimensions.map(String) }),
|
|
25
|
+
...(operation.kind === 'dataset' && operation.measures !== undefined
|
|
26
|
+
? { measures: operation.measures.map(String) }
|
|
27
|
+
: {}),
|
|
28
|
+
...(filters.length > 0 ? { filters } : {}),
|
|
29
|
+
...(orderBy.length > 0 ? { orderBy } : {}),
|
|
30
|
+
// An omitted limit becomes the budget, so a caller cannot ask for an
|
|
31
|
+
// unbounded scan by leaving it out.
|
|
32
|
+
limit: operation.limit ?? maxRows,
|
|
33
|
+
...(operation.offset === undefined ? {} : { offset: operation.offset }),
|
|
34
|
+
...(operation.by === undefined ? {} : { by: operation.by }),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProtocolDatasetMetric } from '@hypequery/protocol';
|
|
2
|
+
import type { MetricHandle, MetricQuery } from '../types.js';
|
|
3
|
+
/** Runtime restrictions travel with a cloned handle, including publication aliases. */
|
|
4
|
+
export declare function protocolMetricCapabilityErrors(handle: MetricHandle, query: MetricQuery): string[];
|
|
5
|
+
/** Pin discovery and execution together, preserving restrictions when graining. */
|
|
6
|
+
export declare function withContractCapabilities(handle: MetricHandle, metric: ProtocolDatasetMetric): MetricHandle;
|
|
7
|
+
//# sourceMappingURL=protocol-metric-capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-metric-capabilities.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAwB,MAAM,aAAa,CAAC;AAUnF,uFAAuF;AACvF,wBAAgB,8BAA8B,CAAC,MAAM,EAAE,YAAY,EAAE,KAAK,EAAE,WAAW,GAAG,MAAM,EAAE,CAajG;AAED,mFAAmF;AACnF,wBAAgB,wBAAwB,CAAC,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,qBAAqB,GAAG,YAAY,CAkC1G"}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const CAPABILITIES = Symbol('hypequery.protocol-metric-capabilities');
|
|
2
|
+
/** Runtime restrictions travel with a cloned handle, including publication aliases. */
|
|
3
|
+
export function protocolMetricCapabilityErrors(handle, query) {
|
|
4
|
+
const capabilities = handle[CAPABILITIES];
|
|
5
|
+
if (!capabilities)
|
|
6
|
+
return [];
|
|
7
|
+
const errors = [];
|
|
8
|
+
for (const dimension of query.dimensions ?? []) {
|
|
9
|
+
if (!capabilities.dimensions.includes(dimension))
|
|
10
|
+
errors.push(`Dimension "${dimension}" is not published for this metric.`);
|
|
11
|
+
}
|
|
12
|
+
for (const filter of query.filters ?? []) {
|
|
13
|
+
if (!capabilities.filters.includes(filter.field))
|
|
14
|
+
errors.push(`Filter "${filter.field}" is not published for this metric.`);
|
|
15
|
+
}
|
|
16
|
+
const grain = handle.__type === 'grained_metric_ref' ? handle.grain : query.by;
|
|
17
|
+
if (grain && !capabilities.grains.includes(grain))
|
|
18
|
+
errors.push(`Grain "${grain}" is not published for this metric.`);
|
|
19
|
+
return errors;
|
|
20
|
+
}
|
|
21
|
+
/** Pin discovery and execution together, preserving restrictions when graining. */
|
|
22
|
+
export function withContractCapabilities(handle, metric) {
|
|
23
|
+
const capabilities = Object.freeze({
|
|
24
|
+
dimensions: Object.freeze(metric.dimensions.map(String)),
|
|
25
|
+
filters: Object.freeze(metric.filters.map(String)),
|
|
26
|
+
grains: Object.freeze([...metric.grains]),
|
|
27
|
+
});
|
|
28
|
+
const pin = (target) => {
|
|
29
|
+
const contract = () => ({
|
|
30
|
+
...target.contract(),
|
|
31
|
+
dimensions: [...capabilities.dimensions],
|
|
32
|
+
filters: [...capabilities.filters],
|
|
33
|
+
grains: [...capabilities.grains],
|
|
34
|
+
});
|
|
35
|
+
if (target.__type === 'grained_metric_ref') {
|
|
36
|
+
return Object.freeze({ ...target, [CAPABILITIES]: capabilities, metric: pin(target.metric), contract });
|
|
37
|
+
}
|
|
38
|
+
const pinned = {
|
|
39
|
+
...target,
|
|
40
|
+
[CAPABILITIES]: capabilities,
|
|
41
|
+
contract,
|
|
42
|
+
by(grain) {
|
|
43
|
+
if (!capabilities.grains.includes(grain))
|
|
44
|
+
throw new Error(`Grain "${grain}" is not published for this metric.`);
|
|
45
|
+
const grained = target.by(grain);
|
|
46
|
+
return Object.freeze({
|
|
47
|
+
...grained,
|
|
48
|
+
[CAPABILITIES]: capabilities,
|
|
49
|
+
metric: pinned,
|
|
50
|
+
contract: () => ({ ...contract(), kind: 'grained_metric', grain }),
|
|
51
|
+
});
|
|
52
|
+
},
|
|
53
|
+
};
|
|
54
|
+
return Object.freeze(pinned);
|
|
55
|
+
};
|
|
56
|
+
return pin(handle);
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { type ProtocolExpression, type ProtocolMetricDerivation } from '@hypequery/protocol';
|
|
2
|
+
import type { AggregationSpec, DerivedMetricSpec, MetricFilter } from '../types.js';
|
|
3
|
+
export declare function filterExpression(filter: MetricFilter): ProtocolExpression;
|
|
4
|
+
export declare function metricExpression(spec: AggregationSpec | DerivedMetricSpec): ProtocolExpression;
|
|
5
|
+
/**
|
|
6
|
+
* The formula in the shape it was authored in, beside the inlined form.
|
|
7
|
+
*
|
|
8
|
+
* `metricExpression` above substitutes each input's aggregate where the formula
|
|
9
|
+
* named it, which states what the metric means but drops the aliases. Those
|
|
10
|
+
* aliases are the column names of the intermediate aggregate, so a catalog
|
|
11
|
+
* rebuilt without them computes the same number through different SQL.
|
|
12
|
+
*
|
|
13
|
+
* Input order follows `uses` and is not sorted: each entry becomes a column of
|
|
14
|
+
* that intermediate result in this order.
|
|
15
|
+
*/
|
|
16
|
+
export declare function metricDerivation(spec: DerivedMetricSpec): ProtocolMetricDerivation;
|
|
17
|
+
//# sourceMappingURL=protocol-metric-expressions.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-metric-expressions.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-metric-expressions.ts"],"names":[],"mappings":"AAAA,OAAO,EAEgB,KAAK,kBAAkB,EAAE,KAAK,wBAAwB,EAC5E,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAsCpF,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,YAAY,GAAG,kBAAkB,CAgBzE;AA+CD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,eAAe,GAAG,iBAAiB,GAAG,kBAAkB,CAQ9F;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,GAAG,wBAAwB,CASlF"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { parseProtocolIdentifier, parseProtocolQualifiedIdentifier, validateCanonicalValue, } from '@hypequery/protocol';
|
|
2
|
+
function canonicalValue(input) {
|
|
3
|
+
if (Array.isArray(input)) {
|
|
4
|
+
return validateCanonicalValue({
|
|
5
|
+
$hypequery: {
|
|
6
|
+
type: 'array',
|
|
7
|
+
version: 1,
|
|
8
|
+
values: input.map(canonicalValue),
|
|
9
|
+
},
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
if (typeof input === 'object' && input !== null) {
|
|
13
|
+
if ('$hypequery' in input)
|
|
14
|
+
return validateCanonicalValue(input);
|
|
15
|
+
const prototype = Object.getPrototypeOf(input);
|
|
16
|
+
if (prototype !== Object.prototype && prototype !== null) {
|
|
17
|
+
throw new TypeError('Dataset protocol adapter only accepts plain filter values.');
|
|
18
|
+
}
|
|
19
|
+
return validateCanonicalValue({
|
|
20
|
+
$hypequery: {
|
|
21
|
+
type: 'map',
|
|
22
|
+
version: 1,
|
|
23
|
+
entries: Object.entries(input)
|
|
24
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
25
|
+
.map(([key, value]) => [key, canonicalValue(value)]),
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return validateCanonicalValue(input);
|
|
30
|
+
}
|
|
31
|
+
export function filterExpression(filter) {
|
|
32
|
+
const left = {
|
|
33
|
+
kind: 'reference',
|
|
34
|
+
name: parseProtocolQualifiedIdentifier(filter.field),
|
|
35
|
+
};
|
|
36
|
+
const right = {
|
|
37
|
+
kind: 'literal',
|
|
38
|
+
value: canonicalValue(filter.value),
|
|
39
|
+
};
|
|
40
|
+
const result = {
|
|
41
|
+
kind: 'comparison',
|
|
42
|
+
operator: filter.operator,
|
|
43
|
+
left,
|
|
44
|
+
right,
|
|
45
|
+
};
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
function aggregationExpression(spec) {
|
|
49
|
+
const result = {
|
|
50
|
+
kind: 'aggregate',
|
|
51
|
+
aggregation: spec.aggregation,
|
|
52
|
+
field: parseProtocolQualifiedIdentifier(spec.field),
|
|
53
|
+
...(spec.argField !== undefined
|
|
54
|
+
? { argField: parseProtocolQualifiedIdentifier(spec.argField) }
|
|
55
|
+
: {}),
|
|
56
|
+
...(spec.level !== undefined ? { level: spec.level } : {}),
|
|
57
|
+
...(spec.filters?.length
|
|
58
|
+
? { filters: spec.filters.map(filterExpression) }
|
|
59
|
+
: {}),
|
|
60
|
+
};
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
function semanticExpression(expression, references = {}) {
|
|
64
|
+
switch (expression.kind) {
|
|
65
|
+
case 'ref':
|
|
66
|
+
return references[expression.name]
|
|
67
|
+
?? {
|
|
68
|
+
kind: 'reference',
|
|
69
|
+
name: parseProtocolQualifiedIdentifier(expression.name),
|
|
70
|
+
};
|
|
71
|
+
case 'literal':
|
|
72
|
+
return { kind: 'literal', value: canonicalValue(expression.value) };
|
|
73
|
+
case 'binary':
|
|
74
|
+
return {
|
|
75
|
+
kind: 'binary',
|
|
76
|
+
operator: expression.operator,
|
|
77
|
+
left: semanticExpression(expression.left, references),
|
|
78
|
+
right: semanticExpression(expression.right, references),
|
|
79
|
+
};
|
|
80
|
+
case 'function':
|
|
81
|
+
return {
|
|
82
|
+
kind: 'call',
|
|
83
|
+
function: expression.name,
|
|
84
|
+
args: expression.args.map(argument => semanticExpression(argument, references)),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export function metricExpression(spec) {
|
|
89
|
+
if (spec.__type === 'aggregation_spec')
|
|
90
|
+
return aggregationExpression(spec);
|
|
91
|
+
const aliases = Object.fromEntries(Object.keys(spec.uses).map(alias => [alias, alias]));
|
|
92
|
+
const references = Object.fromEntries(Object.entries(spec.uses).map(([alias, metric]) => [
|
|
93
|
+
alias,
|
|
94
|
+
aggregationExpression(metric.spec),
|
|
95
|
+
]));
|
|
96
|
+
return semanticExpression(spec.formula(aliases).expression, references);
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The formula in the shape it was authored in, beside the inlined form.
|
|
100
|
+
*
|
|
101
|
+
* `metricExpression` above substitutes each input's aggregate where the formula
|
|
102
|
+
* named it, which states what the metric means but drops the aliases. Those
|
|
103
|
+
* aliases are the column names of the intermediate aggregate, so a catalog
|
|
104
|
+
* rebuilt without them computes the same number through different SQL.
|
|
105
|
+
*
|
|
106
|
+
* Input order follows `uses` and is not sorted: each entry becomes a column of
|
|
107
|
+
* that intermediate result in this order.
|
|
108
|
+
*/
|
|
109
|
+
export function metricDerivation(spec) {
|
|
110
|
+
const aliases = Object.fromEntries(Object.keys(spec.uses).map(alias => [alias, alias]));
|
|
111
|
+
return {
|
|
112
|
+
inputs: Object.entries(spec.uses).map(([alias, metric]) => ({
|
|
113
|
+
alias: parseProtocolIdentifier(alias),
|
|
114
|
+
expression: aggregationExpression(metric.spec),
|
|
115
|
+
})),
|
|
116
|
+
expression: semanticExpression(spec.formula(aliases).expression),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuilds a derived metric's formula from the contract's authored form.
|
|
3
|
+
*
|
|
4
|
+
* The formula is reconstructed by calling the same helpers in `formulas.ts`
|
|
5
|
+
* that authored it, rather than by compiling the expression to SQL here. That
|
|
6
|
+
* is deliberate: those helpers carry the `toSQL` closures that decide spacing,
|
|
7
|
+
* parenthesisation, and function spelling, so reusing them makes byte-identity
|
|
8
|
+
* structural. A second compiler would be a second place for that spelling to
|
|
9
|
+
* live, and the two would drift the first time either changed.
|
|
10
|
+
*/
|
|
11
|
+
import type { ProtocolMetricDerivation } from '@hypequery/protocol';
|
|
12
|
+
import type { FormulaExpr } from '../types.js';
|
|
13
|
+
type Unsupported = (reason: string) => Error;
|
|
14
|
+
/**
|
|
15
|
+
* The formula function a derived metric spec expects.
|
|
16
|
+
*
|
|
17
|
+
* The returned function maps each declared alias through the caller-supplied
|
|
18
|
+
* `inputs` record, exactly as an authored formula does — both the plan builder
|
|
19
|
+
* and the query-builder path pass alias-to-alias, so the emitted column names
|
|
20
|
+
* are the ones the contract declared.
|
|
21
|
+
*
|
|
22
|
+
* The formula is built once here as well, so a malformed one is refused when
|
|
23
|
+
* the metric is rebuilt rather than on the first query that reaches it.
|
|
24
|
+
*/
|
|
25
|
+
export declare function rehydrateDerivedFormula(derivation: ProtocolMetricDerivation, unsupported: Unsupported): (inputs: Record<string, string>) => FormulaExpr;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=protocol-rehydrate-derivation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-rehydrate-derivation.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-rehydrate-derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAsB,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AAYxF,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAQ/C,KAAK,WAAW,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,KAAK,CAAC;AA8G7C;;;;;;;;;;GAUG;AACH,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,wBAAwB,EACpC,WAAW,EAAE,WAAW,GACvB,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAAK,WAAW,CAGjD"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rebuilds a derived metric's formula from the contract's authored form.
|
|
3
|
+
*
|
|
4
|
+
* The formula is reconstructed by calling the same helpers in `formulas.ts`
|
|
5
|
+
* that authored it, rather than by compiling the expression to SQL here. That
|
|
6
|
+
* is deliberate: those helpers carry the `toSQL` closures that decide spacing,
|
|
7
|
+
* parenthesisation, and function spelling, so reusing them makes byte-identity
|
|
8
|
+
* structural. A second compiler would be a second place for that spelling to
|
|
9
|
+
* live, and the two would drift the first time either changed.
|
|
10
|
+
*/
|
|
11
|
+
import { add, ceil, coalesce, divide, floor, multiply, nullIfZero, round, subtract, } from '../formulas.js';
|
|
12
|
+
const BINARY = {
|
|
13
|
+
add, subtract, multiply, divide,
|
|
14
|
+
};
|
|
15
|
+
function arity(args, expected, name, unsupported) {
|
|
16
|
+
if (args.length !== expected) {
|
|
17
|
+
throw unsupported(`"${name}" takes ${expected} argument(s), received ${args.length}`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function literalNumber(expression, unsupported) {
|
|
21
|
+
if (expression.kind !== 'literal' || typeof expression.value !== 'number') {
|
|
22
|
+
throw unsupported('expected a numeric literal argument');
|
|
23
|
+
}
|
|
24
|
+
return expression.value;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A `coalesce` fallback, the one position that accepts a bare value.
|
|
28
|
+
*
|
|
29
|
+
* `formulas.ts` renders a number through `String(...)` and anything else
|
|
30
|
+
* through the operand path, so both round-trip to the same SQL.
|
|
31
|
+
*/
|
|
32
|
+
function fallback(expression, resolve, unsupported) {
|
|
33
|
+
if (expression.kind !== 'literal')
|
|
34
|
+
return operand(expression, resolve, unsupported);
|
|
35
|
+
if (typeof expression.value !== 'number') {
|
|
36
|
+
throw unsupported('a "coalesce" fallback literal must be numeric');
|
|
37
|
+
}
|
|
38
|
+
return expression.value;
|
|
39
|
+
}
|
|
40
|
+
function operand(expression, resolve, unsupported) {
|
|
41
|
+
switch (expression.kind) {
|
|
42
|
+
case 'reference':
|
|
43
|
+
// An alias, or a column the formula named directly. `resolveArg` in
|
|
44
|
+
// `formulas.ts` passes a string through unchanged, which is what the
|
|
45
|
+
// authored form did with the same name.
|
|
46
|
+
return resolve(String(expression.name));
|
|
47
|
+
case 'binary': {
|
|
48
|
+
const build = BINARY[expression.operator];
|
|
49
|
+
if (build === undefined)
|
|
50
|
+
throw unsupported(`unsupported operator "${expression.operator}"`);
|
|
51
|
+
return build(operand(expression.left, resolve, unsupported), operand(expression.right, resolve, unsupported));
|
|
52
|
+
}
|
|
53
|
+
case 'call':
|
|
54
|
+
switch (expression.function) {
|
|
55
|
+
case 'nullIfZero':
|
|
56
|
+
arity(expression.args, 1, 'nullIfZero', unsupported);
|
|
57
|
+
return nullIfZero(operand(expression.args[0], resolve, unsupported));
|
|
58
|
+
case 'floor':
|
|
59
|
+
arity(expression.args, 1, 'floor', unsupported);
|
|
60
|
+
return floor(operand(expression.args[0], resolve, unsupported));
|
|
61
|
+
case 'ceil':
|
|
62
|
+
arity(expression.args, 1, 'ceil', unsupported);
|
|
63
|
+
return ceil(operand(expression.args[0], resolve, unsupported));
|
|
64
|
+
case 'round':
|
|
65
|
+
arity(expression.args, 2, 'round', unsupported);
|
|
66
|
+
return round(operand(expression.args[0], resolve, unsupported), literalNumber(expression.args[1], unsupported));
|
|
67
|
+
case 'coalesce':
|
|
68
|
+
arity(expression.args, 2, 'coalesce', unsupported);
|
|
69
|
+
return coalesce(operand(expression.args[0], resolve, unsupported), fallback(expression.args[1], resolve, unsupported));
|
|
70
|
+
default:
|
|
71
|
+
throw unsupported(`unsupported function "${String(expression.function)}"`);
|
|
72
|
+
}
|
|
73
|
+
default:
|
|
74
|
+
// A literal outside a `round` precision or `coalesce` fallback, or an
|
|
75
|
+
// aggregate that should have been carried as a named input. Either way
|
|
76
|
+
// the authoring API cannot express it, so rebuilding would be a guess.
|
|
77
|
+
throw unsupported(`a formula cannot contain a "${expression.kind}" expression here`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function buildFormula(derivation, resolve, unsupported) {
|
|
81
|
+
const built = operand(derivation.expression, resolve, unsupported);
|
|
82
|
+
if (typeof built === 'string') {
|
|
83
|
+
// `spec.formula(...)` must return a `FormulaExpr`. A formula that is a bare
|
|
84
|
+
// reference names one of its inputs instead of combining them, which the
|
|
85
|
+
// authoring API cannot produce.
|
|
86
|
+
throw unsupported('a formula must combine its inputs rather than name one');
|
|
87
|
+
}
|
|
88
|
+
return built;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The formula function a derived metric spec expects.
|
|
92
|
+
*
|
|
93
|
+
* The returned function maps each declared alias through the caller-supplied
|
|
94
|
+
* `inputs` record, exactly as an authored formula does — both the plan builder
|
|
95
|
+
* and the query-builder path pass alias-to-alias, so the emitted column names
|
|
96
|
+
* are the ones the contract declared.
|
|
97
|
+
*
|
|
98
|
+
* The formula is built once here as well, so a malformed one is refused when
|
|
99
|
+
* the metric is rebuilt rather than on the first query that reaches it.
|
|
100
|
+
*/
|
|
101
|
+
export function rehydrateDerivedFormula(derivation, unsupported) {
|
|
102
|
+
buildFormula(derivation, name => name, unsupported);
|
|
103
|
+
return inputs => buildFormula(derivation, name => inputs[name] ?? name, unsupported);
|
|
104
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { ProtocolExpression } from '@hypequery/protocol';
|
|
2
|
+
import type { MetricFilter } from '../types.js';
|
|
3
|
+
/**
|
|
4
|
+
* A measure's fixed filters cross the contract boundary as RFC 0003
|
|
5
|
+
* expressions. `protocol-adapter.ts` only ever emits `reference <op> literal`
|
|
6
|
+
* for them, so this inverts exactly that shape and refuses anything else rather
|
|
7
|
+
* than partially interpreting an expression it cannot faithfully rebuild.
|
|
8
|
+
*/
|
|
9
|
+
export declare function rehydrateMeasureFilter(expression: ProtocolExpression, unsupported: () => Error): MetricFilter;
|
|
10
|
+
//# sourceMappingURL=protocol-rehydrate-filters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-rehydrate-filters.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-rehydrate-filters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAC9D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAEhD;;;;;GAKG;AACH,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,kBAAkB,EAC9B,WAAW,EAAE,MAAM,KAAK,GACvB,YAAY,CASd"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A measure's fixed filters cross the contract boundary as RFC 0003
|
|
3
|
+
* expressions. `protocol-adapter.ts` only ever emits `reference <op> literal`
|
|
4
|
+
* for them, so this inverts exactly that shape and refuses anything else rather
|
|
5
|
+
* than partially interpreting an expression it cannot faithfully rebuild.
|
|
6
|
+
*/
|
|
7
|
+
export function rehydrateMeasureFilter(expression, unsupported) {
|
|
8
|
+
if (expression.kind !== 'comparison')
|
|
9
|
+
throw unsupported();
|
|
10
|
+
const { left, right, operator } = expression;
|
|
11
|
+
if (left.kind !== 'reference' || right.kind !== 'literal')
|
|
12
|
+
throw unsupported();
|
|
13
|
+
return {
|
|
14
|
+
field: String(left.name),
|
|
15
|
+
operator,
|
|
16
|
+
value: fromCanonicalValue(right.value, unsupported),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Unwraps the RFC 0001 tagged value model back to the plain JavaScript value a
|
|
21
|
+
* measure filter was authored with.
|
|
22
|
+
*/
|
|
23
|
+
function fromCanonicalValue(value, unsupported) {
|
|
24
|
+
if (value === null || typeof value !== 'object')
|
|
25
|
+
return value;
|
|
26
|
+
const tagged = value
|
|
27
|
+
.$hypequery;
|
|
28
|
+
if (tagged === undefined)
|
|
29
|
+
throw unsupported();
|
|
30
|
+
if (tagged.type === 'array') {
|
|
31
|
+
return (tagged.values ?? []).map(item => fromCanonicalValue(item, unsupported));
|
|
32
|
+
}
|
|
33
|
+
if (tagged.value === undefined)
|
|
34
|
+
throw unsupported();
|
|
35
|
+
return tagged.value;
|
|
36
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { ProtocolSemanticMetadata } from '@hypequery/protocol';
|
|
2
|
+
import type { SemanticMetadata } from '../types.js';
|
|
3
|
+
/** Map local semantic metadata onto its portable protocol representation. */
|
|
4
|
+
export declare function toProtocolSemanticMetadata(metadata: SemanticMetadata): ProtocolSemanticMetadata;
|
|
5
|
+
//# sourceMappingURL=protocol-semantic-metadata.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-semantic-metadata.d.ts","sourceRoot":"","sources":["../../src/utils/protocol-semantic-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAGpD,6EAA6E;AAC7E,wBAAgB,0BAA0B,CACxC,QAAQ,EAAE,gBAAgB,GACzB,wBAAwB,CAE1B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"published-name.d.ts","sourceRoot":"","sources":["../../src/utils/published-name.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,GAAG,QAAQ,GAAG,IAAI,CAMlF"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
const PUBLISHED_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
2
|
+
/** Validate names that become public dataset, metric, tool, or schema identifiers. */
|
|
3
|
+
export function assertPublishedName(name, kind) {
|
|
4
|
+
if (!PUBLISHED_NAME.test(name)) {
|
|
5
|
+
throw new Error(`Published ${kind} name "${name}" must start with a letter or underscore and contain only letters, numbers, and underscores.`);
|
|
6
|
+
}
|
|
7
|
+
}
|