@hypequery/datasets 0.14.0 → 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/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -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 +5 -97
- package/dist/protocol-rehydrate.d.ts +10 -0
- package/dist/protocol-rehydrate.d.ts.map +1 -1
- package/dist/protocol-rehydrate.js +62 -16
- package/dist/semantic-query-schema.d.ts +12 -0
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +6 -1
- 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-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/package.json +3 -3
package/dist/index.d.ts
CHANGED
|
@@ -19,6 +19,8 @@ export { rehydrateProtocolDatasets, UnsupportedContractFeatureError, } from './p
|
|
|
19
19
|
export type { RehydratedDataset, RehydrateProtocolDatasetsOptions, } from './protocol-rehydrate.js';
|
|
20
20
|
export { buildProtocolDatasetContract } from './protocol-adapter.js';
|
|
21
21
|
export type { BuildProtocolDatasetContractOptions } from './protocol-adapter.js';
|
|
22
|
+
export { createPortableSemanticExecutor, PortableExecutionBudgetError, PortableExecutionTenantError, PortableExecutionUnsupportedError, } from './portable-executor.js';
|
|
23
|
+
export type { PortableSemanticBudget, PortableSemanticExecutionInput, PortableSemanticExecutorOptions, } from './portable-executor.js';
|
|
22
24
|
export { compilePortableSqlExpression, DEFAULT_SQL_PORTABILITY_LIMITS, } from './sql-portability.js';
|
|
23
25
|
export type { SqlPortabilityIssue, SqlPortabilityIssueCode, SqlPortabilityLimits, SqlPortabilityOptions, SqlPortabilityResult, } from './sql-portability.js';
|
|
24
26
|
export type { SemanticContract, SerializeSemanticContractOptions, ContractDataset, ContractDimension, ContractMeasure, ContractMetric, ContractFilter, ContractRelationship, } from './contract.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -26,6 +26,8 @@ export { serializeSemanticContract, contractToStableJson, hashContract, SEMANTIC
|
|
|
26
26
|
// Portable deployment contract adapter
|
|
27
27
|
export { rehydrateProtocolDatasets, UnsupportedContractFeatureError, } from './protocol-rehydrate.js';
|
|
28
28
|
export { buildProtocolDatasetContract } from './protocol-adapter.js';
|
|
29
|
+
// Portable native execution of a semantic invocation (decision 0005).
|
|
30
|
+
export { createPortableSemanticExecutor, PortableExecutionBudgetError, PortableExecutionTenantError, PortableExecutionUnsupportedError, } from './portable-executor.js';
|
|
29
31
|
// SQL portability compiler (R1A-07)
|
|
30
32
|
export { compilePortableSqlExpression, DEFAULT_SQL_PORTABILITY_LIMITS, } from './sql-portability.js';
|
|
31
33
|
// Agent/tool metadata
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error that deliberately claims a portable failure category.
|
|
3
|
+
*
|
|
4
|
+
* A data plane forwards the category and message of an error carrying this
|
|
5
|
+
* marker instead of reporting a generic executor failure, so the marker — not
|
|
6
|
+
* the shape of the error — is what grants that. A provider exception cannot
|
|
7
|
+
* acquire one by accident, which is the point: the two packages are siblings
|
|
8
|
+
* and cannot share a class, but a raw driver error must never be able to pass
|
|
9
|
+
* itself off as a deliberate claim and put its own message in front of a
|
|
10
|
+
* caller.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately not exported. The three errors below are, because this package's
|
|
13
|
+
* own executor throws them; this base is only how they share the marker. A
|
|
14
|
+
* supported way for someone else's executor to claim a category belongs beside
|
|
15
|
+
* the data plane that honours it, not beside one implementation of the slot it
|
|
16
|
+
* fills.
|
|
17
|
+
*/
|
|
18
|
+
declare abstract class PortableExecutionError extends Error {
|
|
19
|
+
/** The opt-in a data plane checks before trusting anything below it. */
|
|
20
|
+
readonly hypequerySemanticFailure: true;
|
|
21
|
+
abstract readonly code: string;
|
|
22
|
+
abstract readonly category: string;
|
|
23
|
+
}
|
|
24
|
+
/** Signalled when portable execution cannot faithfully serve a target. */
|
|
25
|
+
export declare class PortableExecutionUnsupportedError extends PortableExecutionError {
|
|
26
|
+
readonly code = "HQ_SEMANTIC_UNSUPPORTED_CAPABILITY";
|
|
27
|
+
readonly category = "unsupported-capability";
|
|
28
|
+
constructor(message: string, options?: {
|
|
29
|
+
cause?: unknown;
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export declare class PortableExecutionBudgetError extends PortableExecutionError {
|
|
33
|
+
readonly code = "HQ_SEMANTIC_BUDGET_EXCEEDED";
|
|
34
|
+
readonly category = "budget-exceeded";
|
|
35
|
+
constructor(message: string);
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Signalled when tenancy cannot be enforced for a call that resolved a tenant.
|
|
39
|
+
*
|
|
40
|
+
* `configuration-invalid` rather than a caller-facing category: nothing the
|
|
41
|
+
* caller sent is wrong, and nothing it can send would make the call succeed.
|
|
42
|
+
*/
|
|
43
|
+
export declare class PortableExecutionTenantError extends PortableExecutionError {
|
|
44
|
+
readonly code = "HQ_SEMANTIC_TENANT_UNENFORCEABLE";
|
|
45
|
+
readonly category = "configuration-invalid";
|
|
46
|
+
constructor(message: string);
|
|
47
|
+
}
|
|
48
|
+
export {};
|
|
49
|
+
//# sourceMappingURL=portable-execution-errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-execution-errors.d.ts","sourceRoot":"","sources":["../src/portable-execution-errors.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,uBAAe,sBAAuB,SAAQ,KAAK;IACjD,wEAAwE;IACxE,QAAQ,CAAC,wBAAwB,EAAG,IAAI,CAAU;IAClD,QAAQ,CAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;CACpC;AAED,0EAA0E;AAC1E,qBAAa,iCAAkC,SAAQ,sBAAsB;IAC3E,QAAQ,CAAC,IAAI,wCAAwC;IACrD,QAAQ,CAAC,QAAQ,4BAA4B;gBAEjC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAI/D;AAED,qBAAa,4BAA6B,SAAQ,sBAAsB;IACtE,QAAQ,CAAC,IAAI,iCAAiC;IAC9C,QAAQ,CAAC,QAAQ,qBAAqB;gBAE1B,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,qBAAa,4BAA6B,SAAQ,sBAAsB;IACtE,QAAQ,CAAC,IAAI,sCAAsC;IACnD,QAAQ,CAAC,QAAQ,2BAA2B;gBAEhC,OAAO,EAAE,MAAM;CAI5B"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* An error that deliberately claims a portable failure category.
|
|
3
|
+
*
|
|
4
|
+
* A data plane forwards the category and message of an error carrying this
|
|
5
|
+
* marker instead of reporting a generic executor failure, so the marker — not
|
|
6
|
+
* the shape of the error — is what grants that. A provider exception cannot
|
|
7
|
+
* acquire one by accident, which is the point: the two packages are siblings
|
|
8
|
+
* and cannot share a class, but a raw driver error must never be able to pass
|
|
9
|
+
* itself off as a deliberate claim and put its own message in front of a
|
|
10
|
+
* caller.
|
|
11
|
+
*
|
|
12
|
+
* Deliberately not exported. The three errors below are, because this package's
|
|
13
|
+
* own executor throws them; this base is only how they share the marker. A
|
|
14
|
+
* supported way for someone else's executor to claim a category belongs beside
|
|
15
|
+
* the data plane that honours it, not beside one implementation of the slot it
|
|
16
|
+
* fills.
|
|
17
|
+
*/
|
|
18
|
+
class PortableExecutionError extends Error {
|
|
19
|
+
/** The opt-in a data plane checks before trusting anything below it. */
|
|
20
|
+
hypequerySemanticFailure = true;
|
|
21
|
+
}
|
|
22
|
+
/** Signalled when portable execution cannot faithfully serve a target. */
|
|
23
|
+
export class PortableExecutionUnsupportedError extends PortableExecutionError {
|
|
24
|
+
code = 'HQ_SEMANTIC_UNSUPPORTED_CAPABILITY';
|
|
25
|
+
category = 'unsupported-capability';
|
|
26
|
+
constructor(message, options = {}) {
|
|
27
|
+
super(message, options);
|
|
28
|
+
this.name = 'PortableExecutionUnsupportedError';
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
export class PortableExecutionBudgetError extends PortableExecutionError {
|
|
32
|
+
code = 'HQ_SEMANTIC_BUDGET_EXCEEDED';
|
|
33
|
+
category = 'budget-exceeded';
|
|
34
|
+
constructor(message) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.name = 'PortableExecutionBudgetError';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Signalled when tenancy cannot be enforced for a call that resolved a tenant.
|
|
41
|
+
*
|
|
42
|
+
* `configuration-invalid` rather than a caller-facing category: nothing the
|
|
43
|
+
* caller sent is wrong, and nothing it can send would make the call succeed.
|
|
44
|
+
*/
|
|
45
|
+
export class PortableExecutionTenantError extends PortableExecutionError {
|
|
46
|
+
code = 'HQ_SEMANTIC_TENANT_UNENFORCEABLE';
|
|
47
|
+
category = 'configuration-invalid';
|
|
48
|
+
constructor(message) {
|
|
49
|
+
super(message);
|
|
50
|
+
this.name = 'PortableExecutionTenantError';
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export { PortableExecutionUnsupportedError, PortableExecutionBudgetError, PortableExecutionTenantError } from './portable-execution-errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Portable native execution of a semantic invocation.
|
|
4
|
+
*
|
|
5
|
+
* Decision 0005 chose this over a supervised runtime binding: resolve the
|
|
6
|
+
* dataset or metric from the validated active contract, rebuild its catalog
|
|
7
|
+
* with `rehydrateProtocolDatasets`, and plan the query with the existing
|
|
8
|
+
* semantic planner. No customer module is loaded and no isolated runtime is
|
|
9
|
+
* required, so customer code only ever runs on the author's machine at deploy
|
|
10
|
+
* time.
|
|
11
|
+
*
|
|
12
|
+
* The input is typed structurally rather than against
|
|
13
|
+
* `@hypequery/deployment`'s execution input, so this package stays a sibling of
|
|
14
|
+
* that one. A caller wires the returned function into the data plane's injected
|
|
15
|
+
* `execute` slot.
|
|
16
|
+
*/
|
|
17
|
+
import type { ProtocolDatasetContract, ProtocolDatasetMetric, ProtocolSemanticInvocationResult, ProtocolSemanticQuery } from '@hypequery/protocol';
|
|
18
|
+
import type { QueryBuilderFactoryInput } from './query-builder-protocol.js';
|
|
19
|
+
/** Ceilings the data plane already reduced to the most restrictive value. */
|
|
20
|
+
export interface PortableSemanticBudget {
|
|
21
|
+
readonly maxRows: number;
|
|
22
|
+
readonly deadlineMs?: number;
|
|
23
|
+
readonly maxResponseBytes?: number;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Structurally compatible with the deployment data plane's execution input.
|
|
27
|
+
* Only the fields portable execution reads are declared, so the two packages
|
|
28
|
+
* stay independent.
|
|
29
|
+
*/
|
|
30
|
+
export interface PortableSemanticExecutionInput {
|
|
31
|
+
readonly deployment: {
|
|
32
|
+
readonly datasets: readonly ProtocolDatasetContract[];
|
|
33
|
+
};
|
|
34
|
+
readonly dataset: ProtocolDatasetContract;
|
|
35
|
+
readonly metric?: ProtocolDatasetMetric;
|
|
36
|
+
readonly operation: ProtocolSemanticQuery;
|
|
37
|
+
/** Resolved by the provider callback; never caller-supplied. */
|
|
38
|
+
readonly tenant: unknown;
|
|
39
|
+
readonly budget: PortableSemanticBudget;
|
|
40
|
+
/** Identifies the immutable generation, and keys the rebuilt catalog. */
|
|
41
|
+
readonly activationRevision: string;
|
|
42
|
+
readonly signal?: AbortSignal;
|
|
43
|
+
}
|
|
44
|
+
export interface PortableSemanticExecutorOptions {
|
|
45
|
+
readonly queryBuilder: QueryBuilderFactoryInput;
|
|
46
|
+
/**
|
|
47
|
+
* Rebuilt catalogs are memoized by activation revision. A release is
|
|
48
|
+
* immutable and content-addressed, so the revision is an exact key with no
|
|
49
|
+
* invalidation problem. Only the most recent generation is kept here;
|
|
50
|
+
* `CLOUD-09` owns the bounded multi-release cache.
|
|
51
|
+
*/
|
|
52
|
+
readonly cacheRebuiltCatalogs?: boolean;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Build the executor the deployment data plane injects.
|
|
56
|
+
*
|
|
57
|
+
* Returns the portable result record; the data plane validates it before it
|
|
58
|
+
* reaches a caller.
|
|
59
|
+
*/
|
|
60
|
+
export declare function createPortableSemanticExecutor(options: PortableSemanticExecutorOptions): (input: PortableSemanticExecutionInput) => Promise<ProtocolSemanticInvocationResult>;
|
|
61
|
+
//# sourceMappingURL=portable-executor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"portable-executor.d.ts","sourceRoot":"","sources":["../src/portable-executor.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iCAAiC,EAAE,4BAA4B,EAAE,4BAA4B,EAAE,MAAM,gCAAgC,CAAC;AAK/I;;;;;;;;;;;;;;GAcG;AAEH,OAAO,KAAK,EACV,uBAAuB,EACvB,qBAAqB,EACrB,gCAAgC,EAChC,qBAAqB,EACtB,MAAM,qBAAqB,CAAC;AAO7B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;AAC5E,6EAA6E;AAC7E,MAAM,WAAW,sBAAsB;IACrC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,QAAQ,EAAE,SAAS,uBAAuB,EAAE,CAAA;KAAE,CAAC;IAC/E,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAC;IAC1C,QAAQ,CAAC,MAAM,CAAC,EAAE,qBAAqB,CAAC;IACxC,QAAQ,CAAC,SAAS,EAAE,qBAAqB,CAAC;IAC1C,gEAAgE;IAChE,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,sBAAsB,CAAC;IACxC,yEAAyE;IACzE,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,+BAA+B;IAC9C,QAAQ,CAAC,YAAY,EAAE,wBAAwB,CAAC;IAChD;;;;;OAKG;IACH,QAAQ,CAAC,oBAAoB,CAAC,EAAE,OAAO,CAAC;CACzC;AAID;;;;;GAKG;AACH,wBAAgB,8BAA8B,CAC5C,OAAO,EAAE,+BAA+B,GACvC,CAAC,KAAK,EAAE,8BAA8B,KAAK,OAAO,CAAC,gCAAgC,CAAC,CAgGtF"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { PortableExecutionUnsupportedError, PortableExecutionTenantError } from './portable-execution-errors.js';
|
|
2
|
+
export { PortableExecutionUnsupportedError, PortableExecutionBudgetError, PortableExecutionTenantError } from './portable-execution-errors.js';
|
|
3
|
+
import { tenantRuntime, semanticQuery } from './utils/portable-semantic-query.js';
|
|
4
|
+
import { bounded } from './utils/portable-result-budget.js';
|
|
5
|
+
import { withDeadline } from './utils/portable-execution-deadline.js';
|
|
6
|
+
import { createDatasetClient } from './executor.js';
|
|
7
|
+
import { rehydrateProtocolDatasets, UnsupportedContractFeatureError, } from './protocol-rehydrate.js';
|
|
8
|
+
/**
|
|
9
|
+
* Build the executor the deployment data plane injects.
|
|
10
|
+
*
|
|
11
|
+
* Returns the portable result record; the data plane validates it before it
|
|
12
|
+
* reaches a caller.
|
|
13
|
+
*/
|
|
14
|
+
export function createPortableSemanticExecutor(options) {
|
|
15
|
+
const client = createDatasetClient({ queryBuilder: options.queryBuilder });
|
|
16
|
+
let memo;
|
|
17
|
+
function registryFor(input) {
|
|
18
|
+
if (options.cacheRebuiltCatalogs !== false && memo?.revision === input.activationRevision) {
|
|
19
|
+
return memo.registry;
|
|
20
|
+
}
|
|
21
|
+
let registry;
|
|
22
|
+
try {
|
|
23
|
+
// Skip rather than throw: a single derived metric anywhere in the
|
|
24
|
+
// contract must not make every other dataset unexecutable. The requested
|
|
25
|
+
// target is still refused below when it is one of the skipped ones.
|
|
26
|
+
registry = rehydrateProtocolDatasets(input.deployment.datasets, {
|
|
27
|
+
onUnsupportedMetric: 'skip',
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
if (error instanceof UnsupportedContractFeatureError) {
|
|
32
|
+
// Decision 0005: a surface portable execution cannot reproduce is
|
|
33
|
+
// excluded from it, never approximated.
|
|
34
|
+
throw new PortableExecutionUnsupportedError(error.message, { cause: error });
|
|
35
|
+
}
|
|
36
|
+
throw error;
|
|
37
|
+
}
|
|
38
|
+
memo = { revision: input.activationRevision, registry };
|
|
39
|
+
return registry;
|
|
40
|
+
}
|
|
41
|
+
return async function execute(input) {
|
|
42
|
+
if (input.metric?.kind === 'derived-metric' && input.metric.derivation === undefined) {
|
|
43
|
+
// A derived metric is executable once the contract carries the formula in
|
|
44
|
+
// the shape it was authored in. One written before that field existed
|
|
45
|
+
// still states only what the metric means, not the aliases its SQL is
|
|
46
|
+
// written in terms of, so it stays excluded rather than approximated.
|
|
47
|
+
throw new PortableExecutionUnsupportedError(`Metric "${String(input.metric.name)}" is derived, and this deployment contract does not `
|
|
48
|
+
+ 'carry the authored formula portable execution needs to plan it.');
|
|
49
|
+
}
|
|
50
|
+
const registry = registryFor(input);
|
|
51
|
+
const rebuilt = registry[String(input.dataset.name)];
|
|
52
|
+
if (rebuilt === undefined) {
|
|
53
|
+
throw new PortableExecutionUnsupportedError(`Dataset "${String(input.dataset.name)}" is not part of the activated contract.`);
|
|
54
|
+
}
|
|
55
|
+
const target = input.metric === undefined
|
|
56
|
+
? rebuilt
|
|
57
|
+
: rebuilt.metrics[String(input.metric.name)];
|
|
58
|
+
if (target === undefined) {
|
|
59
|
+
throw new PortableExecutionUnsupportedError(`Metric "${String(input.metric?.name)}" could not be rebuilt from the contract.`);
|
|
60
|
+
}
|
|
61
|
+
const tenant = tenantRuntime(input.tenant);
|
|
62
|
+
// A tenant with nothing to scope by is the one failure that is silent
|
|
63
|
+
// everywhere else: the runtime accepts it, the planner emits no predicate,
|
|
64
|
+
// and the query reads every tenant while each layer believes tenancy was
|
|
65
|
+
// enforced. Contract validation refuses that shape, but this executor is
|
|
66
|
+
// exported on its own and typed structurally, so it must refuse it too
|
|
67
|
+
// rather than trusting the caller to have validated the contract.
|
|
68
|
+
if (tenant !== undefined && rebuilt.tenantKey === undefined) {
|
|
69
|
+
throw new PortableExecutionTenantError(`Dataset "${String(input.dataset.name)}" declares no tenant field, so a resolved tenant `
|
|
70
|
+
+ 'cannot be applied to it.');
|
|
71
|
+
}
|
|
72
|
+
const query = semanticQuery(input.operation, input.budget.maxRows);
|
|
73
|
+
const output = await withDeadline(input.budget, input.signal, async (signal) => (await client.execute(target, query, {
|
|
74
|
+
abortSignal: signal,
|
|
75
|
+
...(tenant === undefined ? {} : { runtime: { tenant } }),
|
|
76
|
+
})));
|
|
77
|
+
const rows = output.data ?? [];
|
|
78
|
+
const pagination = output.meta?.pagination;
|
|
79
|
+
return bounded(Object.freeze({
|
|
80
|
+
kind: 'hypequery-semantic-invocation-result',
|
|
81
|
+
version: 1,
|
|
82
|
+
activationRevision: input.activationRevision,
|
|
83
|
+
data: rows,
|
|
84
|
+
meta: {
|
|
85
|
+
rowCount: rows.length,
|
|
86
|
+
...(pagination === undefined ? {} : { pagination }),
|
|
87
|
+
},
|
|
88
|
+
}), input.budget);
|
|
89
|
+
};
|
|
90
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-adapter.d.ts","sourceRoot":"","sources":["../src/protocol-adapter.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"protocol-adapter.d.ts","sourceRoot":"","sources":["../src/protocol-adapter.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,uBAAuB,EAE5B,KAAK,sBAAsB,EAG5B,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,kBAAkB,EAElB,YAAY,EAEb,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,OAAO,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,sBAAsB,CAAC;IAC3C,QAAQ,CAAC,eAAe,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAAC,CAAC;CAC7E;AAsFD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,mCAAwC,GAChD,uBAAuB,CAiHzB"}
|
package/dist/protocol-adapter.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { filterExpression, metricExpression, metricDerivation } from './utils/protocol-metric-expressions.js';
|
|
2
|
+
import { parseProtocolIdentifier, parseProtocolQualifiedIdentifier, validateProtocolDatasetContract, } from '@hypequery/protocol';
|
|
2
3
|
import { SEMANTIC_FILTER_OPERATORS } from './constants.js';
|
|
3
4
|
import { toProtocolSemanticMetadata } from './utils/protocol-semantic-metadata.js';
|
|
4
5
|
function byName(left, right) {
|
|
@@ -17,107 +18,11 @@ function fieldSchema(type) {
|
|
|
17
18
|
return { kind: 'any' };
|
|
18
19
|
}
|
|
19
20
|
}
|
|
20
|
-
function canonicalValue(input) {
|
|
21
|
-
if (Array.isArray(input)) {
|
|
22
|
-
return validateCanonicalValue({
|
|
23
|
-
$hypequery: {
|
|
24
|
-
type: 'array',
|
|
25
|
-
version: 1,
|
|
26
|
-
values: input.map(canonicalValue),
|
|
27
|
-
},
|
|
28
|
-
});
|
|
29
|
-
}
|
|
30
|
-
if (typeof input === 'object' && input !== null) {
|
|
31
|
-
if ('$hypequery' in input)
|
|
32
|
-
return validateCanonicalValue(input);
|
|
33
|
-
const prototype = Object.getPrototypeOf(input);
|
|
34
|
-
if (prototype !== Object.prototype && prototype !== null) {
|
|
35
|
-
throw new TypeError('Dataset protocol adapter only accepts plain filter values.');
|
|
36
|
-
}
|
|
37
|
-
return validateCanonicalValue({
|
|
38
|
-
$hypequery: {
|
|
39
|
-
type: 'map',
|
|
40
|
-
version: 1,
|
|
41
|
-
entries: Object.entries(input)
|
|
42
|
-
.sort(([left], [right]) => left.localeCompare(right))
|
|
43
|
-
.map(([key, value]) => [key, canonicalValue(value)]),
|
|
44
|
-
},
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
return validateCanonicalValue(input);
|
|
48
|
-
}
|
|
49
|
-
function filterExpression(filter) {
|
|
50
|
-
const left = {
|
|
51
|
-
kind: 'reference',
|
|
52
|
-
name: parseProtocolQualifiedIdentifier(filter.field),
|
|
53
|
-
};
|
|
54
|
-
const right = {
|
|
55
|
-
kind: 'literal',
|
|
56
|
-
value: canonicalValue(filter.value),
|
|
57
|
-
};
|
|
58
|
-
const result = {
|
|
59
|
-
kind: 'comparison',
|
|
60
|
-
operator: filter.operator,
|
|
61
|
-
left,
|
|
62
|
-
right,
|
|
63
|
-
};
|
|
64
|
-
return result;
|
|
65
|
-
}
|
|
66
|
-
function aggregationExpression(spec) {
|
|
67
|
-
const result = {
|
|
68
|
-
kind: 'aggregate',
|
|
69
|
-
aggregation: spec.aggregation,
|
|
70
|
-
field: parseProtocolQualifiedIdentifier(spec.field),
|
|
71
|
-
...(spec.argField !== undefined
|
|
72
|
-
? { argField: parseProtocolQualifiedIdentifier(spec.argField) }
|
|
73
|
-
: {}),
|
|
74
|
-
...(spec.level !== undefined ? { level: spec.level } : {}),
|
|
75
|
-
...(spec.filters?.length
|
|
76
|
-
? { filters: spec.filters.map(filterExpression) }
|
|
77
|
-
: {}),
|
|
78
|
-
};
|
|
79
|
-
return result;
|
|
80
|
-
}
|
|
81
|
-
function semanticExpression(expression, references = {}) {
|
|
82
|
-
switch (expression.kind) {
|
|
83
|
-
case 'ref':
|
|
84
|
-
return references[expression.name]
|
|
85
|
-
?? {
|
|
86
|
-
kind: 'reference',
|
|
87
|
-
name: parseProtocolQualifiedIdentifier(expression.name),
|
|
88
|
-
};
|
|
89
|
-
case 'literal':
|
|
90
|
-
return { kind: 'literal', value: canonicalValue(expression.value) };
|
|
91
|
-
case 'binary':
|
|
92
|
-
return {
|
|
93
|
-
kind: 'binary',
|
|
94
|
-
operator: expression.operator,
|
|
95
|
-
left: semanticExpression(expression.left, references),
|
|
96
|
-
right: semanticExpression(expression.right, references),
|
|
97
|
-
};
|
|
98
|
-
case 'function':
|
|
99
|
-
return {
|
|
100
|
-
kind: 'call',
|
|
101
|
-
function: expression.name,
|
|
102
|
-
args: expression.args.map(argument => semanticExpression(argument, references)),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
21
|
function unwrapMetric(metric) {
|
|
107
22
|
return metric.__type === 'grained_metric_ref'
|
|
108
23
|
? { ref: metric.metric, grain: metric.grain }
|
|
109
24
|
: { ref: metric };
|
|
110
25
|
}
|
|
111
|
-
function metricExpression(spec) {
|
|
112
|
-
if (spec.__type === 'aggregation_spec')
|
|
113
|
-
return aggregationExpression(spec);
|
|
114
|
-
const aliases = Object.fromEntries(Object.keys(spec.uses).map(alias => [alias, alias]));
|
|
115
|
-
const references = Object.fromEntries(Object.entries(spec.uses).map(([alias, metric]) => [
|
|
116
|
-
alias,
|
|
117
|
-
aggregationExpression(metric.spec),
|
|
118
|
-
]));
|
|
119
|
-
return semanticExpression(spec.formula(aliases).expression, references);
|
|
120
|
-
}
|
|
121
26
|
function metricContract(exposedName, metric, endpoint) {
|
|
122
27
|
const { ref, grain } = unwrapMetric(metric);
|
|
123
28
|
const contract = metric.contract();
|
|
@@ -129,6 +34,9 @@ function metricContract(exposedName, metric, endpoint) {
|
|
|
129
34
|
? 'derived-metric'
|
|
130
35
|
: 'metric',
|
|
131
36
|
expression: metricExpression(ref.spec),
|
|
37
|
+
...(ref.spec.__type === 'derived_metric_spec'
|
|
38
|
+
? { derivation: metricDerivation(ref.spec) }
|
|
39
|
+
: {}),
|
|
132
40
|
dimensions: [...contract.dimensions].sort().map(parseProtocolQualifiedIdentifier),
|
|
133
41
|
filters: [...contract.filters].sort().map(parseProtocolIdentifier),
|
|
134
42
|
grains: [...contract.grains].sort(),
|
|
@@ -23,6 +23,16 @@ export interface RehydrateProtocolDatasetsOptions {
|
|
|
23
23
|
* an operational concern of whoever runs the query, not of the deployment.
|
|
24
24
|
*/
|
|
25
25
|
readonly cache?: DatasetCachePolicy;
|
|
26
|
+
/**
|
|
27
|
+
* What to do with a metric portable execution cannot rebuild.
|
|
28
|
+
*
|
|
29
|
+
* `throw` (the default) refuses the whole contract, which is right when a
|
|
30
|
+
* caller is checking whether a deployment is fully portable. `skip` omits
|
|
31
|
+
* just that metric, which is what a runtime wants: one derived metric must
|
|
32
|
+
* not make every other dataset in the deployment unexecutable. A caller that
|
|
33
|
+
* skips is responsible for refusing the skipped target at the point of use.
|
|
34
|
+
*/
|
|
35
|
+
readonly onUnsupportedMetric?: 'throw' | 'skip';
|
|
26
36
|
}
|
|
27
37
|
/**
|
|
28
38
|
* Thrown when a contract describes something portable execution cannot rebuild.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"protocol-rehydrate.d.ts","sourceRoot":"","sources":["../src/protocol-rehydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,uBAAuB,
|
|
1
|
+
{"version":3,"file":"protocol-rehydrate.d.ts","sourceRoot":"","sources":["../src/protocol-rehydrate.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,uBAAuB,EAKxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAKlB,YAAY,EAIb,MAAM,YAAY,CAAC;AAMpB,kFAAkF;AAClF,MAAM,MAAM,iBAAiB,GAAG,kBAAkB,GAAG;IACnD,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC;CAC1D,CAAC;AAEF,MAAM,WAAW,gCAAgC;IAC/C;;;OAGG;IACH,QAAQ,CAAC,KAAK,CAAC,EAAE,kBAAkB,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACjD;AAED;;;;;GAKG;AACH,qBAAa,+BAAgC,SAAQ,KAAK;IACxD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;gBAEb,WAAW,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAMjE;AAwOD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,uBAAuB,EAAE,EAC7C,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CAqE7C"}
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import { dataset } from './dataset.js';
|
|
15
15
|
import { snapshotSemanticMetadata } from './utils/semantic-metadata.js';
|
|
16
16
|
import { withContractCapabilities } from './utils/protocol-metric-capabilities.js';
|
|
17
|
+
import { rehydrateDerivedFormula } from './utils/protocol-rehydrate-derivation.js';
|
|
17
18
|
import { rehydrateMeasureFilter } from './utils/protocol-rehydrate-filters.js';
|
|
18
19
|
/**
|
|
19
20
|
* Thrown when a contract describes something portable execution cannot rebuild.
|
|
@@ -119,38 +120,75 @@ function aggregationKey(value) {
|
|
|
119
120
|
value.filters ?? [],
|
|
120
121
|
]);
|
|
121
122
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
const expression = metric.expression;
|
|
123
|
+
/**
|
|
124
|
+
* The declared measure an aggregate expression was built from.
|
|
125
|
+
*
|
|
126
|
+
* A metric's expression names an aggregation and a field, not the measure that
|
|
127
|
+
* produced it, so the measure is recovered by matching aggregation identity.
|
|
128
|
+
*/
|
|
129
|
+
function measureForAggregate(instance, contract, subject, expression) {
|
|
130
130
|
if (expression.kind !== 'aggregate') {
|
|
131
|
-
throw new UnsupportedContractFeatureError(instance.name,
|
|
131
|
+
throw new UnsupportedContractFeatureError(instance.name, subject, `expected an aggregate expression, received "${expression.kind}"`);
|
|
132
132
|
}
|
|
133
133
|
const field = String(expression.field);
|
|
134
134
|
const candidates = contract.measures.filter(measure => (aggregationKey(measure) === aggregationKey(expression)));
|
|
135
135
|
if (candidates.length === 0) {
|
|
136
|
-
throw new UnsupportedContractFeatureError(instance.name,
|
|
136
|
+
throw new UnsupportedContractFeatureError(instance.name, subject, `no declared measure matches ${expression.aggregation}(${field})`);
|
|
137
137
|
}
|
|
138
138
|
// A metric expression carries no raw SQL, so two measures that share an
|
|
139
139
|
// aggregation identity but override SQL differently are indistinguishable in
|
|
140
140
|
// contract v1. Binding to either would be a guess at which SQL to emit.
|
|
141
141
|
const distinctSql = new Set(candidates.map(measure => measure.sql?.sql ?? null));
|
|
142
142
|
if (distinctSql.size > 1) {
|
|
143
|
-
throw new UnsupportedContractFeatureError(instance.name,
|
|
143
|
+
throw new UnsupportedContractFeatureError(instance.name, subject, `${candidates.map(measure => `"${String(measure.name)}"`).join(' and ')} share `
|
|
144
144
|
+ `${expression.aggregation}(${field}) but emit different SQL, so the contract cannot `
|
|
145
145
|
+ 'say which one this metric was built from');
|
|
146
146
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
return String(candidates[0].name);
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Rebuilds the `uses` map and formula of a derived metric.
|
|
151
|
+
*
|
|
152
|
+
* Each input becomes a base metric named by its alias, because that alias is
|
|
153
|
+
* what both plan builders emit as the column of the intermediate aggregate.
|
|
154
|
+
*/
|
|
155
|
+
function derivedMetricConfig(instance, contract, subject, derivation) {
|
|
156
|
+
const unsupported = (reason) => new UnsupportedContractFeatureError(instance.name, subject, reason);
|
|
157
|
+
const uses = Object.fromEntries(derivation.inputs.map(input => {
|
|
158
|
+
const alias = String(input.alias);
|
|
159
|
+
return [
|
|
160
|
+
alias,
|
|
161
|
+
instance.metric(alias, {
|
|
162
|
+
measure: measureForAggregate(instance, contract, `${subject} input "${alias}"`, input.expression),
|
|
163
|
+
}),
|
|
164
|
+
];
|
|
165
|
+
}));
|
|
166
|
+
return { uses, formula: rehydrateDerivedFormula(derivation, unsupported) };
|
|
167
|
+
}
|
|
168
|
+
function rehydrateMetric(instance, contract, metric) {
|
|
169
|
+
const name = String(metric.name);
|
|
170
|
+
const subject = `metric "${name}"`;
|
|
171
|
+
const derived = metric.expression.kind !== 'aggregate';
|
|
172
|
+
if (derived && metric.derivation === undefined) {
|
|
173
|
+
// The inlined expression states what a derived metric means but drops the
|
|
174
|
+
// aliases its SQL is written in terms of. Without them a rebuild computes
|
|
175
|
+
// the same number through different SQL, which decision 0005 excludes.
|
|
176
|
+
throw new UnsupportedContractFeatureError(instance.name, subject, 'a derived metric requires its authored formula, which this contract does not carry');
|
|
177
|
+
}
|
|
178
|
+
const metadata = {
|
|
150
179
|
...snapshotSemanticMetadata(metric),
|
|
151
180
|
...(metric.label !== undefined ? { label: metric.label } : {}),
|
|
152
181
|
...(metric.description !== undefined ? { description: metric.description } : {}),
|
|
153
|
-
}
|
|
182
|
+
};
|
|
183
|
+
const base = (metric.derivation === undefined
|
|
184
|
+
? instance.metric(name, {
|
|
185
|
+
measure: measureForAggregate(instance, contract, subject, metric.expression),
|
|
186
|
+
...metadata,
|
|
187
|
+
})
|
|
188
|
+
: instance.metric(name, {
|
|
189
|
+
...derivedMetricConfig(instance, contract, subject, metric.derivation),
|
|
190
|
+
...metadata,
|
|
191
|
+
}));
|
|
154
192
|
const handle = metric.grain === undefined
|
|
155
193
|
? base
|
|
156
194
|
: base.by(metric.grain);
|
|
@@ -210,7 +248,15 @@ export function rehydrateProtocolDatasets(contracts, options = {}) {
|
|
|
210
248
|
const instance = instances.get(name);
|
|
211
249
|
const metrics = {};
|
|
212
250
|
for (const metric of contract.metrics) {
|
|
213
|
-
|
|
251
|
+
try {
|
|
252
|
+
metrics[String(metric.name)] = rehydrateMetric(instance, contract, metric);
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
if (options.onUnsupportedMetric !== 'skip'
|
|
256
|
+
|| !(error instanceof UnsupportedContractFeatureError)) {
|
|
257
|
+
throw error;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
214
260
|
}
|
|
215
261
|
registry[name] = Object.assign(Object.create(Object.getPrototypeOf(instance)), instance, { metrics });
|
|
216
262
|
}
|
|
@@ -19,6 +19,18 @@ export interface SemanticQuerySchemaOptions extends SemanticQuerySchemaLimits {
|
|
|
19
19
|
requireSelection?: boolean;
|
|
20
20
|
/** Validate result limits in the schema. Disable when a consumer clamps them. */
|
|
21
21
|
enforceResultLimit?: boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Datasets that may be queried directly. Defaults to all of them.
|
|
24
|
+
*
|
|
25
|
+
* A dataset outside this list still contributes its metrics and can still be
|
|
26
|
+
* joined to, but is not offered as a `query_dataset` target. That split is not
|
|
27
|
+
* hypothetical: a deployment contract authorizes a dataset and each of its
|
|
28
|
+
* metrics through separate endpoint policies, so a caller can be entitled to
|
|
29
|
+
* a metric on a dataset it may not query directly. Compiling one schema for
|
|
30
|
+
* both would either advertise a target the caller cannot use or hide a metric
|
|
31
|
+
* it can.
|
|
32
|
+
*/
|
|
33
|
+
queryableDatasets?: readonly string[];
|
|
22
34
|
}
|
|
23
35
|
/** Metric-specific query capabilities used when they are not embedded in the Dataset source. */
|
|
24
36
|
export type SemanticMetricQueryContract = Pick<MetricCatalogEntry, 'dimensions' | 'filters' | 'grains' | 'grain'>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-query-schema.d.ts","sourceRoot":"","sources":["../src/semantic-query-schema.ts"],"names":[],"mappings":"AAEA,OAAO,EAAK,KAAK,UAAU,EAAE,MAAM,KAAK,CAAC;AAEzC,OAAO,EAIL,KAAK,cAAc,EACnB,KAAK,oBAAoB,EACzB,KAAK,kBAAkB,EACxB,MAAM,cAAc,CAAC;AAEtB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,MAAM,WAAW,yBAAyB;IACxC,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,0BAA2B,SAAQ,yBAAyB;IAC3E,iFAAiF;IACjF,UAAU,CAAC,EAAE,IAAI,GAAG,OAAO,CAAC;IAC5B,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,qEAAqE;IACrE,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAC3B,iFAAiF;IACjF,kBAAkB,CAAC,EAAE,OAAO,CAAC;IAC7B;;;;;;;;;;OAUG;IACH,iBAAiB,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CACvC;AAED,gGAAgG;AAChG,MAAM,MAAM,2BAA2B,GAAG,IAAI,CAC5C,kBAAkB,EAClB,YAAY,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAC9C,CAAC;AAEF,eAAO,MAAM,oCAAoC;;;;;;;EAO/C,CAAC;AAEH,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,YAAY,EAAE,UAAU,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,UAAU,CAAC;IACjC,QAAQ,CAAC,sBAAsB,EAAE,UAAU,CAAC;IAC5C,QAAQ,CAAC,qBAAqB,EAAE,UAAU,CAAC;IAC3C,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,oFAAoF;AACpF,MAAM,MAAM,yBAAyB,GAAG,oBAAoB,GAAG,cAAc,CAAC;AA6I9E,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,yBAAyB,EAClC,OAAO,GAAE,0BAA+B,GACvC,UAAU,CAOZ;AAED,wBAAgB,sBAAsB,CACpC,OAAO,EAAE,yBAAyB,EAClC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,0BAA+B,EACxC,cAAc,CAAC,EAAE,2BAA2B,GAC3C,UAAU,CAaZ;AAsBD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE;IAAE,gBAAgB,CAAC,EAAE,OAAO,CAAA;CAAO,GAC3C,UAAU,CAMZ;AAyBD,wBAAgB,kCAAkC,CAChD,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,EACnD,OAAO,GAAE,0BAA+B,GACvC,6BAA6B,CAgD/B"}
|
|
@@ -187,9 +187,14 @@ function addSelectionRequirement(schema) {
|
|
|
187
187
|
export function buildCanonicalSemanticQuerySchemas(datasets, options = {}) {
|
|
188
188
|
const datasetSchemas = [];
|
|
189
189
|
const metricSchemas = [];
|
|
190
|
+
const queryable = options.queryableDatasets === undefined
|
|
191
|
+
? undefined
|
|
192
|
+
: new Set(options.queryableDatasets);
|
|
190
193
|
for (const [datasetName, dataset] of Object.entries(datasets)
|
|
191
194
|
.sort(([left], [right]) => compareStrings(left, right))) {
|
|
192
|
-
|
|
195
|
+
if (queryable === undefined || queryable.has(datasetName)) {
|
|
196
|
+
datasetSchemas.push(withSelectors(buildDatasetInputSchema(dataset, { ...options, requireSelection: false }), { dataset: z.literal(datasetName) }, options.requireSelection !== false));
|
|
197
|
+
}
|
|
193
198
|
const catalog = resolveCatalog(dataset);
|
|
194
199
|
for (const metricName of Object.keys(catalog.metrics).sort(compareStrings)) {
|
|
195
200
|
metricSchemas.push(withSelectors(buildMetricInputSchema(dataset, metricName, options, catalog.metrics[metricName]), { dataset: z.literal(datasetName), metric: z.literal(metricName) }, false));
|
|
@@ -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,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
|
+
}
|
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,7 +42,7 @@
|
|
|
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
47
|
"zod": "^3.22.4",
|
|
48
48
|
"zod-to-json-schema": "^3.23.5"
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"typescript": "^5.7.3",
|
|
61
61
|
"@vitest/coverage-v8": "^3.2.6",
|
|
62
62
|
"vitest": "^3.2.6",
|
|
63
|
-
"@hypequery/protocol-conformance": "^0.11.
|
|
63
|
+
"@hypequery/protocol-conformance": "^0.11.1"
|
|
64
64
|
},
|
|
65
65
|
"repository": {
|
|
66
66
|
"type": "git",
|