@hypequery/datasets 0.19.0 → 0.21.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/agent-catalog.d.ts +2 -1
- package/dist/agent-catalog.d.ts.map +1 -1
- package/dist/api.type-test.js +22 -1
- package/dist/cache/definition-identity.d.ts +26 -0
- package/dist/cache/definition-identity.d.ts.map +1 -0
- package/dist/cache/definition-identity.js +164 -0
- package/dist/cache/protocol-cache-keys.d.ts +38 -0
- package/dist/cache/protocol-cache-keys.d.ts.map +1 -0
- package/dist/cache/protocol-cache-keys.js +191 -0
- package/dist/cache/query-signature.d.ts +6 -0
- package/dist/cache/query-signature.d.ts.map +1 -1
- package/dist/cache/query-signature.js +10 -13
- package/dist/cache/semantic-query-cache.d.ts +23 -0
- package/dist/cache/semantic-query-cache.d.ts.map +1 -1
- package/dist/catalog.d.ts +6 -5
- package/dist/catalog.d.ts.map +1 -1
- package/dist/catalog.js +29 -9
- package/dist/contract.d.ts +3 -2
- package/dist/contract.d.ts.map +1 -1
- package/dist/contract.js +2 -0
- package/dist/dataset-query.d.ts.map +1 -1
- package/dist/dataset-query.js +21 -4
- package/dist/dataset.d.ts +2 -2
- package/dist/dataset.d.ts.map +1 -1
- package/dist/dataset.js +9 -4
- package/dist/executor.d.ts +7 -1
- package/dist/executor.d.ts.map +1 -1
- package/dist/executor.js +60 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/measure.d.ts +12 -2
- package/dist/measure.d.ts.map +1 -1
- package/dist/measure.js +11 -1
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +10 -1
- package/dist/protocol-deployment-adapter.d.ts.map +1 -1
- package/dist/protocol-deployment-adapter.js +2 -1
- package/dist/query-planner.d.ts +1 -1
- package/dist/query-planner.d.ts.map +1 -1
- package/dist/query-planner.js +5 -17
- package/dist/semantic-plan.d.ts +0 -1
- package/dist/semantic-plan.d.ts.map +1 -1
- package/dist/semantic-planner.d.ts.map +1 -1
- package/dist/semantic-planner.js +10 -3
- package/dist/semantic-query-schema.d.ts.map +1 -1
- package/dist/semantic-query-schema.js +2 -0
- package/dist/types.d.ts +61 -14
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.d.ts +3 -2
- package/dist/utils/agent-catalog-projection.d.ts.map +1 -1
- package/dist/utils/agent-catalog-projection.js +2 -10
- package/dist/utils/approximate-measures.d.ts +3 -2
- package/dist/utils/approximate-measures.d.ts.map +1 -1
- package/dist/utils/approximate-measures.js +11 -8
- package/dist/utils/composite-time-measure-sql.d.ts +10 -0
- package/dist/utils/composite-time-measure-sql.d.ts.map +1 -0
- package/dist/utils/composite-time-measure-sql.js +60 -0
- package/dist/utils/dataset-contract.d.ts.map +1 -1
- package/dist/utils/dataset-contract.js +2 -1
- package/dist/utils/dataset-definition-validation.d.ts.map +1 -1
- package/dist/utils/dataset-definition-validation.js +6 -24
- package/dist/utils/dataset-derived-query.d.ts.map +1 -1
- package/dist/utils/dataset-derived-query.js +12 -55
- package/dist/utils/dataset-measures.d.ts +13 -6
- package/dist/utils/dataset-measures.d.ts.map +1 -1
- package/dist/utils/dataset-measures.js +25 -4
- package/dist/utils/dataset-metric-ref.d.ts +2 -2
- package/dist/utils/dataset-metric-ref.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.d.ts.map +1 -1
- package/dist/utils/dataset-query-result.js +2 -1
- package/dist/utils/dataset-query-validation.d.ts.map +1 -1
- package/dist/utils/dataset-query-validation.js +7 -3
- package/dist/utils/derived-measure-sql.d.ts +4 -0
- package/dist/utils/derived-measure-sql.d.ts.map +1 -0
- package/dist/utils/derived-measure-sql.js +49 -0
- package/dist/utils/derived-measure-validation.d.ts.map +1 -1
- package/dist/utils/derived-measure-validation.js +7 -27
- package/dist/utils/measure-dependencies.d.ts +8 -0
- package/dist/utils/measure-dependencies.d.ts.map +1 -0
- package/dist/utils/measure-dependencies.js +44 -0
- package/dist/utils/measure-evaluation-graph.d.ts +23 -0
- package/dist/utils/measure-evaluation-graph.d.ts.map +1 -0
- package/dist/utils/measure-evaluation-graph.js +38 -0
- package/dist/utils/measure-time-grains.d.ts +4 -0
- package/dist/utils/measure-time-grains.d.ts.map +1 -0
- package/dist/utils/measure-time-grains.js +18 -0
- package/dist/utils/query-timezone.d.ts +9 -0
- package/dist/utils/query-timezone.d.ts.map +1 -0
- package/dist/utils/query-timezone.js +35 -0
- package/dist/utils/semantic-metadata-validation.js +1 -1
- package/dist/utils/semantic-metadata.d.ts.map +1 -1
- package/dist/utils/semantic-metadata.js +3 -1
- package/dist/utils/shift-measure-definition.d.ts +4 -0
- package/dist/utils/shift-measure-definition.d.ts.map +1 -0
- package/dist/utils/shift-measure-definition.js +9 -0
- package/dist/utils/shift-measure-grains.d.ts +5 -0
- package/dist/utils/shift-measure-grains.d.ts.map +1 -0
- package/dist/utils/shift-measure-grains.js +10 -0
- package/dist/utils/shift-measure-sql.d.ts +21 -0
- package/dist/utils/shift-measure-sql.d.ts.map +1 -0
- package/dist/utils/shift-measure-sql.js +63 -0
- package/dist/utils/shift-measure-validation.d.ts +3 -0
- package/dist/utils/shift-measure-validation.d.ts.map +1 -0
- package/dist/utils/shift-measure-validation.js +20 -0
- package/dist/utils/shift-wall-time-sql.d.ts +4 -0
- package/dist/utils/shift-wall-time-sql.d.ts.map +1 -0
- package/dist/utils/shift-wall-time-sql.js +17 -0
- package/dist/utils/sql-under-joins.d.ts +10 -0
- package/dist/utils/sql-under-joins.d.ts.map +1 -0
- package/dist/utils/sql-under-joins.js +15 -0
- package/dist/utils/time-arithmetic-sql.d.ts +4 -0
- package/dist/utils/time-arithmetic-sql.d.ts.map +1 -0
- package/dist/utils/time-arithmetic-sql.js +9 -0
- package/dist/utils/time-axis-bounds.d.ts +24 -0
- package/dist/utils/time-axis-bounds.d.ts.map +1 -0
- package/dist/utils/time-axis-bounds.js +71 -0
- package/dist/utils/time-axis-calendar-guard.d.ts +9 -0
- package/dist/utils/time-axis-calendar-guard.d.ts.map +1 -0
- package/dist/utils/time-axis-calendar-guard.js +17 -0
- package/dist/utils/time-axis-intervals.d.ts +13 -0
- package/dist/utils/time-axis-intervals.d.ts.map +1 -0
- package/dist/utils/time-axis-intervals.js +81 -0
- package/dist/utils/time-measure-axis-sql.d.ts +13 -0
- package/dist/utils/time-measure-axis-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-axis-sql.js +48 -0
- package/dist/utils/time-measure-axis.d.ts +19 -0
- package/dist/utils/time-measure-axis.d.ts.map +1 -0
- package/dist/utils/time-measure-axis.js +46 -0
- package/dist/utils/time-measure-context-sql.d.ts +6 -0
- package/dist/utils/time-measure-context-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-context-sql.js +21 -0
- package/dist/utils/time-measure-dataset-sql.d.ts +9 -0
- package/dist/utils/time-measure-dataset-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-dataset-sql.js +80 -0
- package/dist/utils/time-measure-leaf-sql.d.ts +14 -0
- package/dist/utils/time-measure-leaf-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-leaf-sql.js +52 -0
- package/dist/utils/time-measure-result-sql.d.ts +6 -0
- package/dist/utils/time-measure-result-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-result-sql.js +30 -0
- package/dist/utils/time-measure-source-sql.d.ts +22 -0
- package/dist/utils/time-measure-source-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-source-sql.js +75 -0
- package/dist/utils/time-measure-values-sql.d.ts +20 -0
- package/dist/utils/time-measure-values-sql.d.ts.map +1 -0
- package/dist/utils/time-measure-values-sql.js +87 -0
- package/dist/utils/time-query-measures.d.ts +6 -0
- package/dist/utils/time-query-measures.d.ts.map +1 -0
- package/dist/utils/time-query-measures.js +24 -0
- package/dist/utils/window-aggregation-sql.d.ts +5 -0
- package/dist/utils/window-aggregation-sql.d.ts.map +1 -0
- package/dist/utils/window-aggregation-sql.js +17 -0
- package/dist/utils/window-catalog-metadata.d.ts +14 -0
- package/dist/utils/window-catalog-metadata.d.ts.map +1 -0
- package/dist/utils/window-catalog-metadata.js +17 -0
- package/dist/utils/window-measure-definition.d.ts +4 -0
- package/dist/utils/window-measure-definition.d.ts.map +1 -0
- package/dist/utils/window-measure-definition.js +11 -0
- package/dist/utils/window-measure-dependencies.d.ts +4 -0
- package/dist/utils/window-measure-dependencies.d.ts.map +1 -0
- package/dist/utils/window-measure-dependencies.js +5 -0
- package/dist/utils/window-measure-validation.d.ts +3 -0
- package/dist/utils/window-measure-validation.d.ts.map +1 -0
- package/dist/utils/window-measure-validation.js +49 -0
- package/dist/utils/window-query-measures.d.ts +4 -0
- package/dist/utils/window-query-measures.d.ts.map +1 -0
- package/dist/utils/window-query-measures.js +17 -0
- package/dist/utils/window-query-signature.d.ts +4 -0
- package/dist/utils/window-query-signature.d.ts.map +1 -0
- package/dist/utils/window-query-signature.js +35 -0
- package/package.json +7 -7
package/dist/agent-catalog.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WindowCatalogMetadata } from './utils/window-catalog-metadata.js';
|
|
1
2
|
import type { ProtocolDeploymentContract } from '@hypequery/protocol';
|
|
2
3
|
import { type DatasetCatalog, type DatasetCatalogMap, type DatasetCatalogSource } from './catalog.js';
|
|
3
4
|
import type { SemanticContract } from './contract.js';
|
|
@@ -10,7 +11,7 @@ export interface AgentCatalogDimension extends SemanticMetadata {
|
|
|
10
11
|
filterable: boolean;
|
|
11
12
|
groupable: boolean;
|
|
12
13
|
}
|
|
13
|
-
export interface AgentCatalogMeasure extends SemanticMetadata {
|
|
14
|
+
export interface AgentCatalogMeasure extends SemanticMetadata, WindowCatalogMetadata {
|
|
14
15
|
name: string;
|
|
15
16
|
label?: string;
|
|
16
17
|
description?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-catalog.d.ts","sourceRoot":"","sources":["../src/agent-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;
|
|
1
|
+
{"version":3,"file":"agent-catalog.d.ts","sourceRoot":"","sources":["../src/agent-catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,oCAAoC,CAAC;AAChF,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,qBAAqB,CAAC;AACtE,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EAC1B,MAAM,cAAc,CAAC;AACtB,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EACV,eAAe,EACf,gBAAgB,EAChB,aAAa,EACb,SAAS,EACT,gBAAgB,EACjB,MAAM,YAAY,CAAC;AAOpB,MAAM,WAAW,qBAAsB,SAAQ,gBAAgB;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,OAAO,CAAC;IACpB,SAAS,EAAE,OAAO,CAAC;CACpB;AAED,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB,EAAE,qBAAqB;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mFAAmF;IACnF,WAAW,CAAC,EAAE,IAAI,CAAC;CACpB;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,kBAAmB,SAAQ,gBAAgB;IAC1D,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC3D,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,CAAC,EAAE,gBAAgB,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,eAAe,CAAC;IAC3B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,UAAU,EAAE,qBAAqB,EAAE,CAAC;IACpC,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAC9B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,mBAAmB,EAAE,CAAC;IACjC,aAAa,EAAE,wBAAwB,EAAE,CAAC;IAC1C,MAAM,EAAE,aAAa,CAAC;CACvB;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,mBAAmB,EAAE,CAAC;CACjC;AAED,MAAM,WAAW,6BAA6B;IAC5C,6EAA6E;IAC7E,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,eAAO,MAAM,+BAA+B,QAAa,CAAC;AAE1D,MAAM,MAAM,2BAA2B,GAAG,QAAQ,CAChD,MAAM,CAAC,MAAM,EAAE,oBAAoB,GAAG,cAAc,CAAC,CACtD,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAC1B,2BAA2B,GAC3B,gBAAgB,GAChB,0BAA0B,CAAC;AAE/B;;;;GAIG;AACH,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,UAAU,EAAE,IAAI,CAAC;CAC3B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,iBAAiB,CAAA;CAAE,GAC1E;IAAE,QAAQ,CAAC,IAAI,EAAE,mBAAmB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAA;CAAE,GAC3E;IAAE,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAA;CAAE,CAAC;AAY5F,oFAAoF;AACpF,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE,6BAAkC,GAC1C,gBAAgB,CA4BlB;AAED;;;;;;;GAOG;AACH,wBAAgB,4BAA4B,CAC1C,OAAO,EAAE,gBAAgB,EACzB,OAAO,GAAE,6BAAkC,GAC1C,IAAI,CASN;AAED;;;GAGG;AACH,wBAAgB,0BAA0B,CACxC,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,gCAAgC,GAC9C,mBAAmB,CAiBrB"}
|
package/dist/api.type-test.js
CHANGED
|
@@ -48,10 +48,31 @@ dataset('derivedDependency', {
|
|
|
48
48
|
measures: {
|
|
49
49
|
revenue: measure.sum('amount'),
|
|
50
50
|
doubled: measure.derived({ uses: { value: 'revenue' }, formula: ({ value }) => add(value, value) }),
|
|
51
|
-
// @ts-expect-error v1 derived inputs cannot depend on another derived measure.
|
|
52
51
|
quadrupled: measure.derived({ uses: { value: 'doubled' }, formula: ({ value }) => add(value, value) }),
|
|
53
52
|
},
|
|
54
53
|
});
|
|
54
|
+
const _WindowOrders = dataset('windowOrders', {
|
|
55
|
+
source: 'orders', timeKey: 'createdAt',
|
|
56
|
+
dimensions: { createdAt: dimension.timestamp(), amount: dimension.number() },
|
|
57
|
+
measures: {
|
|
58
|
+
revenue: measure.sum('amount'),
|
|
59
|
+
trailingRevenue: measure.trailing('revenue', { amount: 7, unit: 'day' }),
|
|
60
|
+
runningRevenue: measure.cumulative('revenue'),
|
|
61
|
+
growth: measure.derived({ uses: { running: 'runningRevenue' }, formula: ({ running }) => add(running, running) }),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
// @ts-expect-error standalone metrics may only target base measures, even in the unified registry.
|
|
65
|
+
_WindowOrders.metric('invalidWindowMetric', { measure: 'runningRevenue' });
|
|
66
|
+
dataset('invalidWindowInput', {
|
|
67
|
+
source: 'orders', timeKey: 'createdAt',
|
|
68
|
+
dimensions: { createdAt: dimension.timestamp(), amount: dimension.number() },
|
|
69
|
+
measures: {
|
|
70
|
+
revenue: measure.sum('amount'),
|
|
71
|
+
growth: measure.derived({ uses: { value: 'revenue' }, formula: ({ value }) => add(value, value) }),
|
|
72
|
+
// @ts-expect-error a window must wrap a base measure.
|
|
73
|
+
invalid: measure.cumulative('growth'),
|
|
74
|
+
},
|
|
75
|
+
});
|
|
55
76
|
const deploymentContract = buildProtocolDeploymentContract([Orders]);
|
|
56
77
|
rehydrateProtocolDeploymentContract(deploymentContract);
|
|
57
78
|
dataset('invalidDependency', {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { AnyDatasetInstance } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* The local definition identity RFC 0009 leaves implementation-defined: a
|
|
4
|
+
* digest of every definition that could affect the rows of a query over
|
|
5
|
+
* `root`, plus the metric spec, cache scope, and timezone when present.
|
|
6
|
+
*
|
|
7
|
+
* Throws when a definition holds a function it cannot describe safely; the
|
|
8
|
+
* caller then runs the query uncached.
|
|
9
|
+
*
|
|
10
|
+
* Entries under it are never shared with released deployments or with other
|
|
11
|
+
* implementations. That costs only misses, and any definition change starts
|
|
12
|
+
* fresh.
|
|
13
|
+
*/
|
|
14
|
+
export declare function localDefinitionIdentity(root: AnyDatasetInstance, extra?: {
|
|
15
|
+
metric?: unknown;
|
|
16
|
+
scope?: string;
|
|
17
|
+
timezone?: string;
|
|
18
|
+
}): string;
|
|
19
|
+
/**
|
|
20
|
+
* Folds a cache scope and a non-default timezone into a deployed definition
|
|
21
|
+
* identity. Both change the rows one release returns (`scope` picks the data
|
|
22
|
+
* source, the timezone moves bucket boundaries), so they must separate keys
|
|
23
|
+
* even when the definitions are identical.
|
|
24
|
+
*/
|
|
25
|
+
export declare function scopedDefinitionIdentity(identity: string, scope: string | undefined, timezone?: string): string;
|
|
26
|
+
//# sourceMappingURL=definition-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"definition-identity.d.ts","sourceRoot":"","sources":["../../src/cache/definition-identity.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,kBAAkB,EAAe,MAAM,aAAa,CAAC;AA8InE;;;;;;;;;;;GAWG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,kBAAkB,EACxB,KAAK,GAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAClE,MAAM,CAWR;AAMD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GAAG,SAAS,EACzB,QAAQ,CAAC,EAAE,MAAM,GAChB,MAAM,CAKR"}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import { sha256 } from '@noble/hashes/sha2';
|
|
2
|
+
import { bytesToHex } from '@noble/hashes/utils';
|
|
3
|
+
import { stableStringify } from '../utils/canonical-json.js';
|
|
4
|
+
const LOCAL_DEFINITION_DOMAIN = 'hypequery.ts.local-definitions.v2\0';
|
|
5
|
+
/**
|
|
6
|
+
* Library-created methods, by the `__type` of the object that carries them.
|
|
7
|
+
* They read only state that is described alongside them, so they contribute
|
|
8
|
+
* nothing of their own.
|
|
9
|
+
*/
|
|
10
|
+
const LIBRARY_METHODS = {
|
|
11
|
+
dataset: ['metric'],
|
|
12
|
+
metric_ref: ['by', 'contract'],
|
|
13
|
+
grained_metric_ref: ['contract'],
|
|
14
|
+
};
|
|
15
|
+
function isDataset(value) {
|
|
16
|
+
return (typeof value === 'object'
|
|
17
|
+
&& value !== null
|
|
18
|
+
&& value.__type === 'dataset'
|
|
19
|
+
&& typeof value.name === 'string');
|
|
20
|
+
}
|
|
21
|
+
function isFormulaExpr(value) {
|
|
22
|
+
return (typeof value === 'object'
|
|
23
|
+
&& value !== null
|
|
24
|
+
&& value.__type === 'formula_expr'
|
|
25
|
+
&& typeof value.toSQL === 'function');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* The formula's own SQL, which derived metrics execute. `null` when it throws:
|
|
29
|
+
* derived measures compile the expression instead, so it need not render.
|
|
30
|
+
*/
|
|
31
|
+
function formulaSql(formula) {
|
|
32
|
+
try {
|
|
33
|
+
return formula.toSQL();
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function undescribable(key) {
|
|
40
|
+
throw new Error(`Cannot derive a local definition identity: the function at "${key ?? '?'}" `
|
|
41
|
+
+ 'is not a formula, relationship target, or library method.');
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Builds a deterministic description of every definition a query over a root
|
|
45
|
+
* dataset could read.
|
|
46
|
+
*
|
|
47
|
+
* A function never contributes its source text: that omits the values it
|
|
48
|
+
* captures, so `make(2)` and `make(3)` over `({ r }) => round(r, digits)`
|
|
49
|
+
* would collide. Instead:
|
|
50
|
+
* - a formula is evaluated, and its resolved expression and SQL are described;
|
|
51
|
+
* - a relationship target contributes its dataset's name, and that dataset is
|
|
52
|
+
* described separately;
|
|
53
|
+
* - a known library method contributes nothing.
|
|
54
|
+
*
|
|
55
|
+
* Any other function throws, so the caller runs the query uncached rather
|
|
56
|
+
* than risk serving another definition's rows.
|
|
57
|
+
*/
|
|
58
|
+
class DefinitionDescriber {
|
|
59
|
+
found = new Map();
|
|
60
|
+
pending = [];
|
|
61
|
+
/** Queues a dataset for description and stands in for it by name. */
|
|
62
|
+
reference(dataset) {
|
|
63
|
+
if (!this.found.has(dataset.name)) {
|
|
64
|
+
this.found.set(dataset.name, dataset);
|
|
65
|
+
this.pending.push(dataset);
|
|
66
|
+
}
|
|
67
|
+
return { dataset: dataset.name };
|
|
68
|
+
}
|
|
69
|
+
/** Describes every queued dataset, including those queued along the way. */
|
|
70
|
+
describeDatasets() {
|
|
71
|
+
const described = {};
|
|
72
|
+
while (this.pending.length > 0) {
|
|
73
|
+
const dataset = this.pending.pop();
|
|
74
|
+
described[dataset.name] = this.describeFields(dataset, new WeakSet());
|
|
75
|
+
}
|
|
76
|
+
return described;
|
|
77
|
+
}
|
|
78
|
+
describe(value, seen, owner, key) {
|
|
79
|
+
if (typeof value === 'function')
|
|
80
|
+
return this.describeFunction(value, seen, owner, key);
|
|
81
|
+
if (value instanceof Date)
|
|
82
|
+
return { date: value.toISOString() };
|
|
83
|
+
if (typeof value === 'bigint')
|
|
84
|
+
return { bigint: value.toString() };
|
|
85
|
+
if (typeof value !== 'object' || value === null)
|
|
86
|
+
return value;
|
|
87
|
+
if (isDataset(value))
|
|
88
|
+
return this.reference(value);
|
|
89
|
+
return this.describeFields(value, seen);
|
|
90
|
+
}
|
|
91
|
+
describeFields(value, seen) {
|
|
92
|
+
if (seen.has(value))
|
|
93
|
+
return '[cycle]';
|
|
94
|
+
seen.add(value);
|
|
95
|
+
const described = Array.isArray(value)
|
|
96
|
+
? value.map((item) => this.describe(item, seen))
|
|
97
|
+
: Object.fromEntries(Object.entries(value).map(([name, item]) => [
|
|
98
|
+
name,
|
|
99
|
+
this.describe(item, seen, value, name),
|
|
100
|
+
]));
|
|
101
|
+
seen.delete(value);
|
|
102
|
+
return described;
|
|
103
|
+
}
|
|
104
|
+
describeFunction(fn, seen, owner, key) {
|
|
105
|
+
if (key === 'target') {
|
|
106
|
+
const target = fn();
|
|
107
|
+
return isDataset(target) ? this.reference(target) : undescribable(key);
|
|
108
|
+
}
|
|
109
|
+
if (key === 'formula' && owner && typeof owner.uses === 'object' && owner.uses !== null) {
|
|
110
|
+
// The same identity aliases the planner and validation evaluate with.
|
|
111
|
+
const aliases = Object.fromEntries(Object.keys(owner.uses).map((alias) => [alias, alias]));
|
|
112
|
+
const result = fn(aliases);
|
|
113
|
+
if (!isFormulaExpr(result))
|
|
114
|
+
return undescribable(key);
|
|
115
|
+
return { expression: this.describe(result.expression, seen), sql: formulaSql(result) };
|
|
116
|
+
}
|
|
117
|
+
const type = typeof owner?.__type === 'string' ? owner.__type : undefined;
|
|
118
|
+
if (type !== undefined && key !== undefined && LIBRARY_METHODS[type]?.includes(key)) {
|
|
119
|
+
return 'method';
|
|
120
|
+
}
|
|
121
|
+
return undescribable(key);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* The local definition identity RFC 0009 leaves implementation-defined: a
|
|
126
|
+
* digest of every definition that could affect the rows of a query over
|
|
127
|
+
* `root`, plus the metric spec, cache scope, and timezone when present.
|
|
128
|
+
*
|
|
129
|
+
* Throws when a definition holds a function it cannot describe safely; the
|
|
130
|
+
* caller then runs the query uncached.
|
|
131
|
+
*
|
|
132
|
+
* Entries under it are never shared with released deployments or with other
|
|
133
|
+
* implementations. That costs only misses, and any definition change starts
|
|
134
|
+
* fresh.
|
|
135
|
+
*/
|
|
136
|
+
export function localDefinitionIdentity(root, extra = {}) {
|
|
137
|
+
const describer = new DefinitionDescriber();
|
|
138
|
+
describer.reference(root);
|
|
139
|
+
const metric = extra.metric === undefined ? null : describer.describe(extra.metric, new WeakSet());
|
|
140
|
+
const description = stableStringify({
|
|
141
|
+
datasets: describer.describeDatasets(),
|
|
142
|
+
metric,
|
|
143
|
+
scope: extra.scope ?? null,
|
|
144
|
+
timezone: extra.timezone ?? null,
|
|
145
|
+
});
|
|
146
|
+
return bytesToHex(sha256(new TextEncoder().encode(LOCAL_DEFINITION_DOMAIN + description)));
|
|
147
|
+
}
|
|
148
|
+
function foldPartition(identity, domain, value) {
|
|
149
|
+
return bytesToHex(sha256(new TextEncoder().encode(`${domain}\0${identity}\0${value}`)));
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Folds a cache scope and a non-default timezone into a deployed definition
|
|
153
|
+
* identity. Both change the rows one release returns (`scope` picks the data
|
|
154
|
+
* source, the timezone moves bucket boundaries), so they must separate keys
|
|
155
|
+
* even when the definitions are identical.
|
|
156
|
+
*/
|
|
157
|
+
export function scopedDefinitionIdentity(identity, scope, timezone) {
|
|
158
|
+
let folded = identity;
|
|
159
|
+
if (scope !== undefined)
|
|
160
|
+
folded = foldPartition(folded, 'hypequery.ts.cache-scope.v1', scope);
|
|
161
|
+
if (timezone !== undefined)
|
|
162
|
+
folded = foldPartition(folded, 'hypequery.ts.cache-timezone.v1', timezone);
|
|
163
|
+
return folded;
|
|
164
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { AnyDatasetInstance, DatasetQuery, ExecutionContext, GrainedMetricRef, MetricQuery, MetricRef } from '../types.js';
|
|
2
|
+
/** Namespace defaults, so caching works without any configuration. */
|
|
3
|
+
export declare const DEFAULT_CACHE_PROJECT = "hypequery";
|
|
4
|
+
export declare const DEFAULT_CACHE_ENVIRONMENT = "default";
|
|
5
|
+
/** Key settings from `SemanticCacheOptions`, resolved once per client. */
|
|
6
|
+
export interface ProtocolCacheKeySettings {
|
|
7
|
+
readonly secret: Uint8Array;
|
|
8
|
+
readonly project: string;
|
|
9
|
+
readonly environment: string;
|
|
10
|
+
readonly keyVersion: number;
|
|
11
|
+
readonly definitionIdentity?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ProtocolCacheKeyOptions {
|
|
14
|
+
secret?: Uint8Array;
|
|
15
|
+
project?: string;
|
|
16
|
+
environment?: string;
|
|
17
|
+
keyVersion?: number;
|
|
18
|
+
definitionIdentity?: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolves and validates key settings at client construction, so a
|
|
22
|
+
* misconfiguration fails at startup rather than silently caching nothing.
|
|
23
|
+
*
|
|
24
|
+
* Without a secret the cache generates a random one for its lifetime. Keys
|
|
25
|
+
* stay HMAC-derived and opaque; entries are simply not shared beyond this
|
|
26
|
+
* client. An explicitly empty or short secret is an error, never a reason to
|
|
27
|
+
* generate one.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolveProtocolCacheKeySettings(options: ProtocolCacheKeyOptions, sharedStore: boolean): ProtocolCacheKeySettings;
|
|
30
|
+
/**
|
|
31
|
+
* The RFC 0013 store key for a dataset query, or `undefined` when the call
|
|
32
|
+
* cannot be cached (for example, a filter value with no portable form).
|
|
33
|
+
* `query` must already carry its effective limit.
|
|
34
|
+
*/
|
|
35
|
+
export declare function datasetCacheKey(settings: ProtocolCacheKeySettings, ds: AnyDatasetInstance, query: DatasetQuery, context: ExecutionContext | undefined, scope: string | undefined): string | undefined;
|
|
36
|
+
/** The RFC 0013 store key for a metric query; see `datasetCacheKey`. */
|
|
37
|
+
export declare function metricCacheKey(settings: ProtocolCacheKeySettings, metric: MetricRef | GrainedMetricRef, query: MetricQuery, context: ExecutionContext | undefined, scope: string | undefined): string | undefined;
|
|
38
|
+
//# sourceMappingURL=protocol-cache-keys.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol-cache-keys.d.ts","sourceRoot":"","sources":["../../src/cache/protocol-cache-keys.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAEhB,WAAW,EACX,SAAS,EACV,MAAM,aAAa,CAAC;AAIrB,sEAAsE;AACtE,eAAO,MAAM,qBAAqB,cAAc,CAAC;AACjD,eAAO,MAAM,yBAAyB,YAAY,CAAC;AAInD,0EAA0E;AAC1E,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;CACtC;AAED,MAAM,WAAW,uBAAuB;IACtC,MAAM,CAAC,EAAE,UAAU,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;CAC7B;AAID;;;;;;;;GAQG;AACH,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,uBAAuB,EAChC,WAAW,EAAE,OAAO,GACnB,wBAAwB,CAqC1B;AAkHD;;;;GAIG;AACH,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,wBAAwB,EAClC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAcpB;AAED,wEAAwE;AACxE,wBAAgB,cAAc,CAC5B,QAAQ,EAAE,wBAAwB,EAClC,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,EAAE,WAAW,EAClB,OAAO,EAAE,gBAAgB,GAAG,SAAS,EACrC,KAAK,EAAE,MAAM,GAAG,SAAS,GACxB,MAAM,GAAG,SAAS,CAgBpB"}
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { buildProtocolCachePreimage, deriveProtocolCacheKey, validateCanonicalValue, } from '@hypequery/protocol';
|
|
2
|
+
import { getMetricGrain, getMetricRef } from '../utils/metric-handle.js';
|
|
3
|
+
import { localDefinitionIdentity, scopedDefinitionIdentity } from './definition-identity.js';
|
|
4
|
+
/** Namespace defaults, so caching works without any configuration. */
|
|
5
|
+
export const DEFAULT_CACHE_PROJECT = 'hypequery';
|
|
6
|
+
export const DEFAULT_CACHE_ENVIRONMENT = 'default';
|
|
7
|
+
const SECRET_BYTES = 32;
|
|
8
|
+
const DEFINITION_IDENTITY = /^[0-9a-f]{64}$/;
|
|
9
|
+
let warnedSharedStoreWithoutSecret = false;
|
|
10
|
+
/**
|
|
11
|
+
* Resolves and validates key settings at client construction, so a
|
|
12
|
+
* misconfiguration fails at startup rather than silently caching nothing.
|
|
13
|
+
*
|
|
14
|
+
* Without a secret the cache generates a random one for its lifetime. Keys
|
|
15
|
+
* stay HMAC-derived and opaque; entries are simply not shared beyond this
|
|
16
|
+
* client. An explicitly empty or short secret is an error, never a reason to
|
|
17
|
+
* generate one.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveProtocolCacheKeySettings(options, sharedStore) {
|
|
20
|
+
let secret = options.secret;
|
|
21
|
+
if (secret === undefined) {
|
|
22
|
+
secret = globalThis.crypto.getRandomValues(new Uint8Array(SECRET_BYTES));
|
|
23
|
+
if (sharedStore && !warnedSharedStoreWithoutSecret) {
|
|
24
|
+
warnedSharedStoreWithoutSecret = true;
|
|
25
|
+
console.warn('[hypequery/cache] No cache secret is configured, so this client generated one. '
|
|
26
|
+
+ 'Other instances sharing this store will not reuse its entries. '
|
|
27
|
+
+ 'Pass the same cache.secret to every instance to share them.');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (options.definitionIdentity !== undefined
|
|
31
|
+
&& !DEFINITION_IDENTITY.test(options.definitionIdentity)) {
|
|
32
|
+
throw new Error('cache.definitionIdentity must be 64 lowercase hexadecimal characters.');
|
|
33
|
+
}
|
|
34
|
+
const settings = {
|
|
35
|
+
secret,
|
|
36
|
+
project: options.project ?? DEFAULT_CACHE_PROJECT,
|
|
37
|
+
environment: options.environment ?? DEFAULT_CACHE_ENVIRONMENT,
|
|
38
|
+
keyVersion: options.keyVersion ?? 1,
|
|
39
|
+
...(options.definitionIdentity !== undefined
|
|
40
|
+
? { definitionIdentity: options.definitionIdentity }
|
|
41
|
+
: {}),
|
|
42
|
+
};
|
|
43
|
+
// Throws ProtocolCacheKeyError for an empty or short secret, an invalid
|
|
44
|
+
// namespace, or an out-of-range key version.
|
|
45
|
+
deriveProtocolCacheKey({
|
|
46
|
+
secret: settings.secret,
|
|
47
|
+
namespace: { project: settings.project, environment: settings.environment },
|
|
48
|
+
keyVersion: settings.keyVersion,
|
|
49
|
+
preimage: '',
|
|
50
|
+
});
|
|
51
|
+
return settings;
|
|
52
|
+
}
|
|
53
|
+
function datetimeValue(value) {
|
|
54
|
+
return validateCanonicalValue({
|
|
55
|
+
$hypequery: {
|
|
56
|
+
type: 'datetime',
|
|
57
|
+
version: 1,
|
|
58
|
+
clickhouseType: 'DateTime64',
|
|
59
|
+
precision: 3,
|
|
60
|
+
timezone: 'UTC',
|
|
61
|
+
value: value.toISOString(),
|
|
62
|
+
},
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
/** A filter value as an RFC 0001 canonical value. Throws when it has none. */
|
|
66
|
+
function canonicalLiteral(value) {
|
|
67
|
+
if (value instanceof Date)
|
|
68
|
+
return datetimeValue(value);
|
|
69
|
+
if (Array.isArray(value)) {
|
|
70
|
+
return validateCanonicalValue({
|
|
71
|
+
$hypequery: { type: 'array', version: 1, values: value.map(canonicalLiteral) },
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
return validateCanonicalValue(value);
|
|
75
|
+
}
|
|
76
|
+
function filterNode(filter) {
|
|
77
|
+
let literal;
|
|
78
|
+
if (filter.operator === 'between' && Array.isArray(filter.value)) {
|
|
79
|
+
// RFC 0003 bounds are a two-item tuple; in/notIn take an array.
|
|
80
|
+
literal = validateCanonicalValue({
|
|
81
|
+
$hypequery: { type: 'tuple', version: 1, values: filter.value.map(canonicalLiteral) },
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
literal = canonicalLiteral(filter.value);
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
kind: 'comparison',
|
|
89
|
+
operator: filter.operator,
|
|
90
|
+
left: { kind: 'reference', name: filter.field },
|
|
91
|
+
right: { kind: 'literal', value: literal },
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
function sharedQueryFields(query) {
|
|
95
|
+
return {
|
|
96
|
+
...(query.dimensions !== undefined ? { dimensions: [...query.dimensions] } : {}),
|
|
97
|
+
...(query.filters !== undefined ? { filters: query.filters.map(filterNode) } : {}),
|
|
98
|
+
...(query.segments !== undefined ? { segments: [...query.segments] } : {}),
|
|
99
|
+
...(query.orderBy !== undefined
|
|
100
|
+
? { orderBy: query.orderBy.map(({ field, direction }) => ({ field, direction })) }
|
|
101
|
+
: {}),
|
|
102
|
+
...(query.limit !== undefined ? { limit: query.limit } : {}),
|
|
103
|
+
...(query.offset !== undefined ? { offset: query.offset } : {}),
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
/** The tenant capability present on the call, whether or not it is applied. */
|
|
107
|
+
function tenantScope(context) {
|
|
108
|
+
const tenant = context?.runtime?.tenant;
|
|
109
|
+
if (tenant === undefined)
|
|
110
|
+
return { mode: 'none' };
|
|
111
|
+
if (typeof tenant === 'string')
|
|
112
|
+
return { mode: 'scoped', ids: [tenant] };
|
|
113
|
+
if ('scope' in tenant && tenant.scope === 'all')
|
|
114
|
+
return { mode: 'all' };
|
|
115
|
+
if ('id' in tenant)
|
|
116
|
+
return { mode: 'scoped', ids: [tenant.id] };
|
|
117
|
+
if ('in' in tenant)
|
|
118
|
+
return { mode: 'scoped', ids: [...tenant.in] };
|
|
119
|
+
// Unrecognized runtime shapes fail preimage validation and run uncached.
|
|
120
|
+
return tenant;
|
|
121
|
+
}
|
|
122
|
+
function deriveKey(settings, query, definitionIdentity, context, rowLimit) {
|
|
123
|
+
const preimage = buildProtocolCachePreimage({
|
|
124
|
+
secret: settings.secret,
|
|
125
|
+
definitionIdentity,
|
|
126
|
+
query,
|
|
127
|
+
tenant: tenantScope(context),
|
|
128
|
+
rowLimit: rowLimit ?? null,
|
|
129
|
+
});
|
|
130
|
+
return deriveProtocolCacheKey({
|
|
131
|
+
secret: settings.secret,
|
|
132
|
+
namespace: { project: settings.project, environment: settings.environment },
|
|
133
|
+
keyVersion: settings.keyVersion,
|
|
134
|
+
preimage,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* RFC 0009's normalized query has no timezone, yet it moves bucket boundaries
|
|
139
|
+
* and local time-key bounds. It is folded into the definition identity, as
|
|
140
|
+
* `scope` is. UTC, the client default, folds in nothing, so default queries
|
|
141
|
+
* keep the keys other runtimes derive for the same release.
|
|
142
|
+
*/
|
|
143
|
+
function partitionTimezone(query) {
|
|
144
|
+
return query.timezone === 'UTC' ? undefined : query.timezone;
|
|
145
|
+
}
|
|
146
|
+
function definitionFor(settings, root, scope, query, metric) {
|
|
147
|
+
const timezone = partitionTimezone(query);
|
|
148
|
+
return settings.definitionIdentity !== undefined
|
|
149
|
+
? scopedDefinitionIdentity(settings.definitionIdentity, scope, timezone)
|
|
150
|
+
: localDefinitionIdentity(root, { metric, scope, timezone });
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* The RFC 0013 store key for a dataset query, or `undefined` when the call
|
|
154
|
+
* cannot be cached (for example, a filter value with no portable form).
|
|
155
|
+
* `query` must already carry its effective limit.
|
|
156
|
+
*/
|
|
157
|
+
export function datasetCacheKey(settings, ds, query, context, scope) {
|
|
158
|
+
try {
|
|
159
|
+
const wire = {
|
|
160
|
+
kind: 'dataset',
|
|
161
|
+
dataset: ds.name,
|
|
162
|
+
...sharedQueryFields(query),
|
|
163
|
+
...(query.measures !== undefined ? { measures: [...query.measures] } : {}),
|
|
164
|
+
...(query.by !== undefined ? { by: query.by } : {}),
|
|
165
|
+
};
|
|
166
|
+
return deriveKey(settings, wire, definitionFor(settings, ds, scope, query), context, query.limit);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
// A cache failure never fails a query: run uncached.
|
|
170
|
+
return undefined;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/** The RFC 0013 store key for a metric query; see `datasetCacheKey`. */
|
|
174
|
+
export function metricCacheKey(settings, metric, query, context, scope) {
|
|
175
|
+
try {
|
|
176
|
+
const ref = getMetricRef(metric);
|
|
177
|
+
const grain = getMetricGrain(metric, query);
|
|
178
|
+
const wire = {
|
|
179
|
+
kind: 'metric',
|
|
180
|
+
dataset: ref.dataset.name,
|
|
181
|
+
metric: ref.name,
|
|
182
|
+
...sharedQueryFields(query),
|
|
183
|
+
...(grain !== undefined ? { by: grain } : {}),
|
|
184
|
+
};
|
|
185
|
+
const definition = definitionFor(settings, ref.dataset, scope, query, ref.spec);
|
|
186
|
+
return deriveKey(settings, wire, definition, context, query.limit);
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
return undefined;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
@@ -14,6 +14,12 @@
|
|
|
14
14
|
import type { AnyDatasetInstance, DatasetQuery, ExecutionContext, GrainedMetricRef, MetricQuery, MetricRef } from '../types.js';
|
|
15
15
|
import { stableStringify } from '../utils/canonical-json.js';
|
|
16
16
|
export { stableStringify };
|
|
17
|
+
/**
|
|
18
|
+
* @deprecated The result cache no longer keys entries by this readable
|
|
19
|
+
* signature. It uses opaque RFC 0009/0013 keys, which never contain tenant ids,
|
|
20
|
+
* filter values, or table names. Kept for callers that used it directly.
|
|
21
|
+
*/
|
|
17
22
|
export declare function buildDatasetQuerySignature(ds: AnyDatasetInstance, query: DatasetQuery, context?: ExecutionContext): string;
|
|
23
|
+
/** @deprecated See `buildDatasetQuerySignature`. */
|
|
18
24
|
export declare function buildMetricQuerySignature(metric: MetricRef | GrainedMetricRef, query: MetricQuery, context?: ExecutionContext): string;
|
|
19
25
|
//# sourceMappingURL=query-signature.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"query-signature.d.ts","sourceRoot":"","sources":["../../src/cache/query-signature.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"query-signature.d.ts","sourceRoot":"","sources":["../../src/cache/query-signature.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EACV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,gBAAgB,EAGhB,WAAW,EACX,SAAS,EACV,MAAM,aAAa,CAAC;AAIrB,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAE7D,OAAO,EAAE,eAAe,EAAE,CAAC;AAsF3B;;;;GAIG;AACH,wBAAgB,0BAA0B,CACxC,EAAE,EAAE,kBAAkB,EACtB,KAAK,EAAE,YAAY,EACnB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAoBR;AAED,oDAAoD;AACpD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,SAAS,GAAG,gBAAgB,EACpC,KAAK,EAAE,WAAW,EAClB,OAAO,CAAC,EAAE,gBAAgB,GACzB,MAAM,CAoBR"}
|
|
@@ -1,16 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Canonical cache keys for semantic queries.
|
|
3
|
-
*
|
|
4
|
-
* A signature captures everything that changes a query's result set: the
|
|
5
|
-
* target (dataset or metric), selected dimensions/measures, filters, ordering,
|
|
6
|
-
* pagination, time grain, the effective tenant scope, and any explicit cache
|
|
7
|
-
* scope (see `SemanticCacheRuntime.scope`). Two calls with the
|
|
8
|
-
* same signature are guaranteed to produce the same rows against the same
|
|
9
|
-
* underlying data, so the signature is safe to use as a result-cache key.
|
|
10
|
-
*
|
|
11
|
-
* Keys are readable canonical strings rather than hashes so stores can inspect
|
|
12
|
-
* them and tests can assert on them; stores are free to hash internally.
|
|
13
|
-
*/
|
|
1
|
+
import { windowQuerySignature } from '../utils/window-query-signature.js';
|
|
14
2
|
import { getMetricGrain, getMetricRef } from '../utils/metric-handle.js';
|
|
15
3
|
import { getRuntimeTenantPredicate } from '../utils/tenant-runtime.js';
|
|
16
4
|
import { buildRelationshipBuilderContext } from '../utils/relationship-builder-plan.js';
|
|
@@ -84,6 +72,11 @@ function segmentSignature(ds, names) {
|
|
|
84
72
|
})),
|
|
85
73
|
};
|
|
86
74
|
}
|
|
75
|
+
/**
|
|
76
|
+
* @deprecated The result cache no longer keys entries by this readable
|
|
77
|
+
* signature. It uses opaque RFC 0009/0013 keys, which never contain tenant ids,
|
|
78
|
+
* filter values, or table names. Kept for callers that used it directly.
|
|
79
|
+
*/
|
|
87
80
|
export function buildDatasetQuerySignature(ds, query, context) {
|
|
88
81
|
return stableStringify({
|
|
89
82
|
v: SIGNATURE_VERSION,
|
|
@@ -93,16 +86,19 @@ export function buildDatasetQuerySignature(ds, query, context) {
|
|
|
93
86
|
dimensions: query.dimensions ?? null,
|
|
94
87
|
// `null` distinguishes the "all measures" default from an explicit [].
|
|
95
88
|
measures: query.measures ?? null,
|
|
89
|
+
...windowQuerySignature(ds, query),
|
|
96
90
|
filters: filterSignature(query.filters),
|
|
97
91
|
...segmentSignature(ds, query.segments),
|
|
98
92
|
orderBy: orderBySignature(query.orderBy),
|
|
99
93
|
by: query.by ?? null,
|
|
94
|
+
...(query.timezone === undefined ? {} : { timezone: query.timezone }),
|
|
100
95
|
limit: query.limit ?? null,
|
|
101
96
|
offset: query.offset ?? null,
|
|
102
97
|
tenant: tenantSignature(ds, query, context),
|
|
103
98
|
scope: scopeSignature(context),
|
|
104
99
|
});
|
|
105
100
|
}
|
|
101
|
+
/** @deprecated See `buildDatasetQuerySignature`. */
|
|
106
102
|
export function buildMetricQuerySignature(metric, query, context) {
|
|
107
103
|
const ref = getMetricRef(metric);
|
|
108
104
|
const grain = getMetricGrain(metric, query);
|
|
@@ -117,6 +113,7 @@ export function buildMetricQuerySignature(metric, query, context) {
|
|
|
117
113
|
...segmentSignature(ref.dataset, query.segments),
|
|
118
114
|
orderBy: orderBySignature(query.orderBy),
|
|
119
115
|
by: grain ?? null,
|
|
116
|
+
...(query.timezone === undefined ? {} : { timezone: query.timezone }),
|
|
120
117
|
limit: query.limit ?? null,
|
|
121
118
|
offset: query.offset ?? null,
|
|
122
119
|
tenant: tenantSignature(ref.dataset, query, context),
|
|
@@ -51,6 +51,29 @@ export interface SemanticCacheOptions {
|
|
|
51
51
|
* so identical queries against different backends never collide.
|
|
52
52
|
*/
|
|
53
53
|
scope?: string;
|
|
54
|
+
/**
|
|
55
|
+
* RFC 0013 cache-key secret: at least 32 random bytes, the same on every
|
|
56
|
+
* instance that shares a store, distinct per environment, never shipped.
|
|
57
|
+
*
|
|
58
|
+
* Optional. Without it the client generates a random secret for its
|
|
59
|
+
* lifetime, so keys stay opaque but entries are not shared with other
|
|
60
|
+
* instances or across restarts. That is all an in-memory store needs; set
|
|
61
|
+
* it for a shared store such as Redis. Keys never contain tenant ids,
|
|
62
|
+
* filter values, or table names either way.
|
|
63
|
+
*/
|
|
64
|
+
secret?: Uint8Array;
|
|
65
|
+
/** Namespace project for RFC 0013 keys. Defaults to `'hypequery'`. */
|
|
66
|
+
project?: string;
|
|
67
|
+
/** Namespace environment for RFC 0013 keys. Defaults to `'default'`. */
|
|
68
|
+
environment?: string;
|
|
69
|
+
/** Increment whenever `secret` rotates; old entries become unreachable. Defaults to 1. */
|
|
70
|
+
keyVersion?: number;
|
|
71
|
+
/**
|
|
72
|
+
* The deployed bundle identity (RFC 0007), to share entries with other
|
|
73
|
+
* runtimes serving the same release, including Python. Without it, a digest
|
|
74
|
+
* of the local definitions is used.
|
|
75
|
+
*/
|
|
76
|
+
definitionIdentity?: string;
|
|
54
77
|
}
|
|
55
78
|
/** Per-call cache controls, passed via `ExecutionContext.cache`. */
|
|
56
79
|
export interface SemanticCacheRuntime {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"semantic-query-cache.d.ts","sourceRoot":"","sources":["../../src/cache/semantic-query-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,OAAO,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,GAAG,SAAS,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC3F,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,kBAAkB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1C,KAAK,CAAC,IAAI,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAChC;AAED,4EAA4E;AAC5E,MAAM,WAAW,oBAAoB;IACnC,+EAA+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,gFAAgF;IAChF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,KAAK,CAAC,EAAE,kBAAkB,CAAC;IAC3B;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"semantic-query-cache.d.ts","sourceRoot":"","sources":["../../src/cache/semantic-query-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/catalog.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type WindowCatalogMetadata } from './utils/window-catalog-metadata.js';
|
|
1
2
|
import type { AnyDatasetInstance, DimensionDefinition, MeasureDefinition, MetricHandle, RelationshipDefinition, SemanticFilterDefinition, DatasetLimits, DatasetDefaults, DatasetFreshness, SemanticMetadata, TimeGrain } from './types.js';
|
|
2
3
|
export interface DimensionCatalogEntry extends SemanticMetadata {
|
|
3
4
|
type: DimensionDefinition['fieldType'];
|
|
@@ -8,9 +9,9 @@ export interface DimensionCatalogEntry extends SemanticMetadata {
|
|
|
8
9
|
filterable: boolean;
|
|
9
10
|
groupable: boolean;
|
|
10
11
|
}
|
|
11
|
-
export interface MeasureCatalogEntry extends SemanticMetadata {
|
|
12
|
-
aggregation
|
|
13
|
-
field
|
|
12
|
+
export interface MeasureCatalogEntry extends SemanticMetadata, WindowCatalogMetadata {
|
|
13
|
+
aggregation?: MeasureDefinition['aggregation'];
|
|
14
|
+
field?: string;
|
|
14
15
|
/** Second column for argMax/argMin. */
|
|
15
16
|
argField?: string;
|
|
16
17
|
/** Percentile level in [0, 1]. */
|
|
@@ -18,11 +19,11 @@ export interface MeasureCatalogEntry extends SemanticMetadata {
|
|
|
18
19
|
sql?: string;
|
|
19
20
|
label?: string;
|
|
20
21
|
description?: string;
|
|
21
|
-
filterCount
|
|
22
|
+
filterCount?: number;
|
|
22
23
|
/** Present, as `true`, when the measure returns an estimate. */
|
|
23
24
|
approximate?: true;
|
|
24
25
|
}
|
|
25
|
-
export interface DerivedMeasureCatalogEntry extends SemanticMetadata {
|
|
26
|
+
export interface DerivedMeasureCatalogEntry extends SemanticMetadata, WindowCatalogMetadata {
|
|
26
27
|
label?: string;
|
|
27
28
|
description?: string;
|
|
28
29
|
/** Present, as `true`, when any measure the formula uses is approximate. */
|