@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.
Files changed (33) hide show
  1. package/dist/index.d.ts +2 -0
  2. package/dist/index.d.ts.map +1 -1
  3. package/dist/index.js +2 -0
  4. package/dist/portable-execution-errors.d.ts +49 -0
  5. package/dist/portable-execution-errors.d.ts.map +1 -0
  6. package/dist/portable-execution-errors.js +52 -0
  7. package/dist/portable-executor.d.ts +61 -0
  8. package/dist/portable-executor.d.ts.map +1 -0
  9. package/dist/portable-executor.js +90 -0
  10. package/dist/protocol-adapter.d.ts.map +1 -1
  11. package/dist/protocol-adapter.js +5 -97
  12. package/dist/protocol-rehydrate.d.ts +10 -0
  13. package/dist/protocol-rehydrate.d.ts.map +1 -1
  14. package/dist/protocol-rehydrate.js +62 -16
  15. package/dist/semantic-query-schema.d.ts +12 -0
  16. package/dist/semantic-query-schema.d.ts.map +1 -1
  17. package/dist/semantic-query-schema.js +6 -1
  18. package/dist/utils/portable-execution-deadline.d.ts +4 -0
  19. package/dist/utils/portable-execution-deadline.d.ts.map +1 -0
  20. package/dist/utils/portable-execution-deadline.js +27 -0
  21. package/dist/utils/portable-result-budget.d.ts +12 -0
  22. package/dist/utils/portable-result-budget.d.ts.map +1 -0
  23. package/dist/utils/portable-result-budget.js +21 -0
  24. package/dist/utils/portable-semantic-query.d.ts +5 -0
  25. package/dist/utils/portable-semantic-query.d.ts.map +1 -0
  26. package/dist/utils/portable-semantic-query.js +36 -0
  27. package/dist/utils/protocol-metric-expressions.d.ts +17 -0
  28. package/dist/utils/protocol-metric-expressions.d.ts.map +1 -0
  29. package/dist/utils/protocol-metric-expressions.js +118 -0
  30. package/dist/utils/protocol-rehydrate-derivation.d.ts +27 -0
  31. package/dist/utils/protocol-rehydrate-derivation.d.ts.map +1 -0
  32. package/dist/utils/protocol-rehydrate-derivation.js +104 -0
  33. 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';
@@ -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":"AAAA,OAAO,EAML,KAAK,uBAAuB,EAE5B,KAAK,sBAAsB,EAI5B,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EAEV,kBAAkB,EAIlB,YAAY,EAEb,MAAM,YAAY,CAAC;AAUpB,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;AAyLD;;;GAGG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,kBAAkB,EAC3B,OAAO,GAAE,mCAAwC,GAChD,uBAAuB,CAiHzB"}
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"}
@@ -1,4 +1,5 @@
1
- import { parseProtocolIdentifier, parseProtocolQualifiedIdentifier, validateCanonicalValue, validateProtocolDatasetContract, } from '@hypequery/protocol';
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,EAGxB,MAAM,qBAAqB,CAAC;AAE7B,OAAO,KAAK,EACV,kBAAkB,EAClB,kBAAkB,EAIlB,YAAY,EAIb,MAAM,YAAY,CAAC;AAKpB,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;CACrC;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;AAmLD;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CACvC,SAAS,EAAE,SAAS,uBAAuB,EAAE,EAC7C,OAAO,GAAE,gCAAqC,GAC7C,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,iBAAiB,CAAC,CAAC,CA8D7C"}
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
- function rehydrateMetric(instance, contract, metric) {
123
- const name = String(metric.name);
124
- if (metric.kind === 'derived-metric') {
125
- // The symbolic expression a derived metric needs is not carried by contract
126
- // v1; CORE-17 adds it. Until then this fails closed rather than guessing.
127
- throw new UnsupportedContractFeatureError(instance.name, `metric "${name}"`, 'a derived metric requires its symbolic expression, which deployment contract v1 does not carry');
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, `metric "${name}"`, `expected an aggregate expression, received "${expression.kind}"`);
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, `metric "${name}"`, `no declared measure matches ${expression.aggregation}(${field})`);
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, `metric "${name}"`, `${candidates.map(measure => `"${String(measure.name)}"`).join(' and ')} share `
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
- const measureName = candidates[0].name;
148
- const base = instance.metric(name, {
149
- measure: measureName,
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
- metrics[String(metric.name)] = rehydrateMetric(instance, contract, metric);
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;CAC9B;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,CA0C/B"}
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
- datasetSchemas.push(withSelectors(buildDatasetInputSchema(dataset, { ...options, requireSelection: false }), { dataset: z.literal(datasetName) }, options.requireSelection !== false));
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.14.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.12.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.0"
63
+ "@hypequery/protocol-conformance": "^0.11.1"
64
64
  },
65
65
  "repository": {
66
66
  "type": "git",