@hypequery/serve 0.6.1 → 0.8.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.
@@ -4,13 +4,13 @@
4
4
  * The generated endpoint is a POST handler that:
5
5
  * - Accepts dimensions, measures, filters, orderBy, limit, offset, by
6
6
  * - Validates requested dimensions/measures/filters against the dataset contract
7
- * - Executes via QueryBuilderFactoryLike
7
+ * - Executes via the shared DatasetClient (result cache included)
8
8
  * - Applies Serve-managed tenant filtering when configured
9
9
  * - Returns { data } or { data, meta } based on headers
10
10
  */
11
11
  import { z } from 'zod';
12
12
  import type { AuthContext, ServeEndpoint } from '../../types.js';
13
- import type { QueryBuilderFactoryLike } from '@hypequery/datasets';
13
+ import type { DatasetClient, QueryBuilderFactoryLike } from '@hypequery/datasets';
14
14
  import { type DatasetEntry } from './utils/dataset-entry.js';
15
15
  export type { DatasetEntry } from './utils/dataset-entry.js';
16
16
  declare const datasetResultSchema: z.ZodObject<{
@@ -33,8 +33,26 @@ declare const datasetResultSchema: z.ZodObject<{
33
33
  offset: number;
34
34
  hasMore: boolean;
35
35
  }>>;
36
+ cache: z.ZodOptional<z.ZodObject<{
37
+ hit: z.ZodBoolean;
38
+ ageMs: z.ZodOptional<z.ZodNumber>;
39
+ stale: z.ZodOptional<z.ZodBoolean>;
40
+ }, "strip", z.ZodTypeAny, {
41
+ hit: boolean;
42
+ ageMs?: number | undefined;
43
+ stale?: boolean | undefined;
44
+ }, {
45
+ hit: boolean;
46
+ ageMs?: number | undefined;
47
+ stale?: boolean | undefined;
48
+ }>>;
36
49
  }, "strip", z.ZodTypeAny, {
37
50
  tenant?: string | undefined;
51
+ cache?: {
52
+ hit: boolean;
53
+ ageMs?: number | undefined;
54
+ stale?: boolean | undefined;
55
+ } | undefined;
38
56
  timingMs?: number | undefined;
39
57
  sql?: string | undefined;
40
58
  rowCount?: number | undefined;
@@ -45,6 +63,11 @@ declare const datasetResultSchema: z.ZodObject<{
45
63
  } | undefined;
46
64
  }, {
47
65
  tenant?: string | undefined;
66
+ cache?: {
67
+ hit: boolean;
68
+ ageMs?: number | undefined;
69
+ stale?: boolean | undefined;
70
+ } | undefined;
48
71
  timingMs?: number | undefined;
49
72
  sql?: string | undefined;
50
73
  rowCount?: number | undefined;
@@ -58,6 +81,11 @@ declare const datasetResultSchema: z.ZodObject<{
58
81
  data: Record<string, unknown>[];
59
82
  meta?: {
60
83
  tenant?: string | undefined;
84
+ cache?: {
85
+ hit: boolean;
86
+ ageMs?: number | undefined;
87
+ stale?: boolean | undefined;
88
+ } | undefined;
61
89
  timingMs?: number | undefined;
62
90
  sql?: string | undefined;
63
91
  rowCount?: number | undefined;
@@ -71,6 +99,11 @@ declare const datasetResultSchema: z.ZodObject<{
71
99
  data: Record<string, unknown>[];
72
100
  meta?: {
73
101
  tenant?: string | undefined;
102
+ cache?: {
103
+ hit: boolean;
104
+ ageMs?: number | undefined;
105
+ stale?: boolean | undefined;
106
+ } | undefined;
74
107
  timingMs?: number | undefined;
75
108
  sql?: string | undefined;
76
109
  rowCount?: number | undefined;
@@ -81,5 +114,5 @@ declare const datasetResultSchema: z.ZodObject<{
81
114
  } | undefined;
82
115
  } | undefined;
83
116
  }>;
84
- export declare function createDatasetEndpoint<TAuth extends AuthContext>(name: string, entry: DatasetEntry<TAuth>, builderFactory: QueryBuilderFactoryLike): ServeEndpoint<z.ZodTypeAny, typeof datasetResultSchema, any, TAuth, any>;
117
+ export declare function createDatasetEndpoint<TAuth extends AuthContext>(name: string, entry: DatasetEntry<TAuth>, analytics: DatasetClient, builderFactory: QueryBuilderFactoryLike): ServeEndpoint<z.ZodTypeAny, typeof datasetResultSchema, any, TAuth, any>;
85
118
  //# sourceMappingURL=dataset-endpoint.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dataset-endpoint.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/dataset-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,WAAW,EAGX,aAAa,EAEd,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAY7B,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGlF,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAkB7D,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGvB,CAAC;AAMH,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,WAAW,EAC7D,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAC1B,cAAc,EAAE,uBAAuB,GACtC,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,mBAAmB,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CAoG1E"}
1
+ {"version":3,"file":"dataset-endpoint.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/dataset-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,WAAW,EAGX,aAAa,EAEd,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EACV,aAAa,EACb,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAGlF,YAAY,EAAE,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAuB7D,QAAA,MAAM,mBAAmB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGvB,CAAC;AAMH,wBAAgB,qBAAqB,CAAC,KAAK,SAAS,WAAW,EAC7D,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,EAC1B,SAAS,EAAE,aAAa,EACxB,cAAc,EAAE,uBAAuB,GACtC,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,mBAAmB,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CA0G1E"}
@@ -4,12 +4,11 @@
4
4
  * The generated endpoint is a POST handler that:
5
5
  * - Accepts dimensions, measures, filters, orderBy, limit, offset, by
6
6
  * - Validates requested dimensions/measures/filters against the dataset contract
7
- * - Executes via QueryBuilderFactoryLike
7
+ * - Executes via the shared DatasetClient (result cache included)
8
8
  * - Applies Serve-managed tenant filtering when configured
9
9
  * - Returns { data } or { data, meta } based on headers
10
10
  */
11
11
  import { z } from 'zod';
12
- import { runDatasetQuery, validateDatasetQuery, } from '@hypequery/datasets/internal';
13
12
  import { ServeHttpError } from '../../errors.js';
14
13
  import { resolveSemanticExecutionRuntime, resolveSemanticQueryBuilder, } from '../query-builder-context.js';
15
14
  import { buildDatasetQueryDescription } from './utils/dataset-query-metadata.js';
@@ -28,6 +27,11 @@ const datasetResultMetaSchema = z.object({
28
27
  offset: z.number(),
29
28
  hasMore: z.boolean(),
30
29
  }).optional(),
30
+ cache: z.object({
31
+ hit: z.boolean(),
32
+ ageMs: z.number().optional(),
33
+ stale: z.boolean().optional(),
34
+ }).optional(),
31
35
  }).optional();
32
36
  const datasetResultSchema = z.object({
33
37
  data: z.array(z.record(z.unknown())),
@@ -36,7 +40,7 @@ const datasetResultSchema = z.object({
36
40
  // ---------------------------------------------------------------------------
37
41
  // Factory
38
42
  // ---------------------------------------------------------------------------
39
- export function createDatasetEndpoint(name, entry, builderFactory) {
43
+ export function createDatasetEndpoint(name, entry, analytics, builderFactory) {
40
44
  const resolved = resolveDatasetEntry(entry);
41
45
  const ds = resolved.dataset;
42
46
  const effectiveMaxLimit = resolved.maxLimit ?? ds.limits?.maxResultSize ?? 1000;
@@ -74,21 +78,27 @@ export function createDatasetEndpoint(name, entry, builderFactory) {
74
78
  offset: input.offset,
75
79
  by: input.by,
76
80
  };
77
- const executionContext = runtime ? { runtime } : undefined;
78
- const validation = validateDatasetQuery(ds, query, executionContext);
81
+ const validationContext = runtime ? { runtime } : undefined;
82
+ const validation = analytics.validate(ds, query, validationContext);
79
83
  if (!validation.valid) {
80
84
  throw new ServeHttpError(400, 'VALIDATION_ERROR', validation.errors.join('; '));
81
85
  }
82
- // Build query
86
+ // Execute through the shared client so per-entry cache TTLs apply.
83
87
  const runtimeBuilderFactory = resolveSemanticQueryBuilder(semanticContext, builderFactory);
84
88
  if (ctx.tenantId) {
85
89
  if (!runtime?.tenant || !ds.tenantKey) {
86
90
  throw new ServeHttpError(500, 'INTERNAL_SERVER_ERROR', `Dataset endpoint "${name}" requires dataset tenantKey and serve tenant runtime when tenant isolation is enabled.`);
87
91
  }
88
92
  }
89
- const result = await runDatasetQuery(ds, query, {
90
- builderFactory: runtimeBuilderFactory,
91
- context: executionContext,
93
+ const result = await analytics.execute(ds, query, {
94
+ runtime: {
95
+ ...runtime,
96
+ builderFactory: runtimeBuilderFactory,
97
+ tenant: runtime?.tenant,
98
+ },
99
+ cache: typeof resolved.cache === 'number' && resolved.cache > 0
100
+ ? { ttlMs: resolved.cache }
101
+ : undefined,
92
102
  });
93
103
  const timingMs = Date.now() - start;
94
104
  // Meta — opt in via the `includeMeta` input field or the x-include-meta header.
@@ -29,8 +29,26 @@ declare const metricResultSchema: z.ZodObject<{
29
29
  offset: number;
30
30
  hasMore: boolean;
31
31
  }>>;
32
+ cache: z.ZodOptional<z.ZodObject<{
33
+ hit: z.ZodBoolean;
34
+ ageMs: z.ZodOptional<z.ZodNumber>;
35
+ stale: z.ZodOptional<z.ZodBoolean>;
36
+ }, "strip", z.ZodTypeAny, {
37
+ hit: boolean;
38
+ ageMs?: number | undefined;
39
+ stale?: boolean | undefined;
40
+ }, {
41
+ hit: boolean;
42
+ ageMs?: number | undefined;
43
+ stale?: boolean | undefined;
44
+ }>>;
32
45
  }, "strip", z.ZodTypeAny, {
33
46
  tenant?: string | undefined;
47
+ cache?: {
48
+ hit: boolean;
49
+ ageMs?: number | undefined;
50
+ stale?: boolean | undefined;
51
+ } | undefined;
34
52
  timingMs?: number | undefined;
35
53
  sql?: string | undefined;
36
54
  rowCount?: number | undefined;
@@ -41,6 +59,11 @@ declare const metricResultSchema: z.ZodObject<{
41
59
  } | undefined;
42
60
  }, {
43
61
  tenant?: string | undefined;
62
+ cache?: {
63
+ hit: boolean;
64
+ ageMs?: number | undefined;
65
+ stale?: boolean | undefined;
66
+ } | undefined;
44
67
  timingMs?: number | undefined;
45
68
  sql?: string | undefined;
46
69
  rowCount?: number | undefined;
@@ -54,6 +77,11 @@ declare const metricResultSchema: z.ZodObject<{
54
77
  data: Record<string, unknown>[];
55
78
  meta?: {
56
79
  tenant?: string | undefined;
80
+ cache?: {
81
+ hit: boolean;
82
+ ageMs?: number | undefined;
83
+ stale?: boolean | undefined;
84
+ } | undefined;
57
85
  timingMs?: number | undefined;
58
86
  sql?: string | undefined;
59
87
  rowCount?: number | undefined;
@@ -67,6 +95,11 @@ declare const metricResultSchema: z.ZodObject<{
67
95
  data: Record<string, unknown>[];
68
96
  meta?: {
69
97
  tenant?: string | undefined;
98
+ cache?: {
99
+ hit: boolean;
100
+ ageMs?: number | undefined;
101
+ stale?: boolean | undefined;
102
+ } | undefined;
70
103
  timingMs?: number | undefined;
71
104
  sql?: string | undefined;
72
105
  rowCount?: number | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"metric-endpoint.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/metric-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EAGZ,WAAW,EACX,aAAa,EACb,eAAe,EACf,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAsB,aAAa,EAAkB,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAwBpI,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAyBH,wBAAgB,kBAAkB,CAAC,KAAK,SAAS,WAAW,EAC1D,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,GACxB;IACD,MAAM,EAAE,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAUA;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,SAAS,WAAW,EAC5D,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,EACzB,SAAS,EAAE,aAAa,EACxB,qBAAqB,EAAE,uBAAuB,GAC7C,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,kBAAkB,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CA4GzE"}
1
+ {"version":3,"file":"metric-endpoint.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/metric-endpoint.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EAGZ,WAAW,EACX,aAAa,EACb,eAAe,EACf,oBAAoB,EACrB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAsB,aAAa,EAAkB,YAAY,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AA6BpI,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAyBH,wBAAgB,kBAAkB,CAAC,KAAK,SAAS,WAAW,EAC1D,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,GACxB;IACD,MAAM,EAAE,YAAY,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC,EAAE,YAAY,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,oBAAoB,CAAC,KAAK,CAAC,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,WAAW,CAAC,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAUA;AAMD,wBAAgB,oBAAoB,CAAC,KAAK,SAAS,WAAW,EAC5D,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,EACzB,SAAS,EAAE,aAAa,EACxB,qBAAqB,EAAE,uBAAuB,GAC7C,aAAa,CAAC,CAAC,CAAC,UAAU,EAAE,OAAO,kBAAkB,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,CAAC,CA+GzE"}
@@ -23,6 +23,11 @@ const metricResultMetaSchema = z.object({
23
23
  offset: z.number(),
24
24
  hasMore: z.boolean(),
25
25
  }).optional(),
26
+ cache: z.object({
27
+ hit: z.boolean(),
28
+ ageMs: z.number().optional(),
29
+ stale: z.boolean().optional(),
30
+ }).optional(),
26
31
  }).optional();
27
32
  const metricResultSchema = z.object({
28
33
  data: z.array(z.record(z.unknown())),
@@ -104,13 +109,16 @@ export function createMetricEndpoint(name, entry, analytics, defaultBuilderFacto
104
109
  if (!validation.valid) {
105
110
  throw new ServeHttpError(400, 'VALIDATION_ERROR', validation.errors.join('; '));
106
111
  }
107
- // Execute with tenant context
112
+ // Execute with tenant context; per-entry cache TTL applies server-side.
108
113
  const result = await analytics.execute(metricRef, query, {
109
114
  runtime: {
110
115
  ...runtime,
111
116
  builderFactory: runtimeBuilderFactory,
112
117
  tenant: runtime?.tenant,
113
118
  },
119
+ cache: typeof resolved.cache === 'number' && resolved.cache > 0
120
+ ? { ttlMs: resolved.cache }
121
+ : undefined,
114
122
  });
115
123
  // Decide whether to include meta — `includeMeta` input field or x-include-meta header.
116
124
  const includeMeta = input.includeMeta === true
@@ -1 +1 @@
1
- {"version":3,"file":"create-api.d.ts","sourceRoot":"","sources":["../../src/server/create-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EAEZ,gBAAgB,EAIhB,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAgCrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACxE,QAAQ,SAAS,aAAa,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC5D,SAAS,SAAS,cAAc,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAE9D,QAAQ,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,KAClE,YAAY,CACb,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,GACvC,wBAAwB,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,EAClE,QAAQ,EACR,KAAK,CAgQN,CAAC"}
1
+ {"version":3,"file":"create-api.d.ts","sourceRoot":"","sources":["../../src/server/create-api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EAEX,YAAY,EAEZ,gBAAgB,EAIhB,eAAe,EACf,WAAW,EACX,aAAa,EACb,cAAc,EACd,wBAAwB,EACzB,MAAM,aAAa,CAAC;AAgCrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,SAAS,GACpB,QAAQ,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAClE,KAAK,SAAS,WAAW,GAAG,WAAW,EACvC,QAAQ,SAAS,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EACxE,QAAQ,SAAS,aAAa,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAC5D,SAAS,SAAS,cAAc,CAAC,KAAK,CAAC,GAAG,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,EAE9D,QAAQ,WAAW,CAAC,QAAQ,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,SAAS,CAAC,KAClE,YAAY,CACb,gBAAgB,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,GACvC,wBAAwB,CAAC,QAAQ,EAAE,SAAS,EAAE,QAAQ,EAAE,KAAK,CAAC,EAClE,QAAQ,EACR,KAAK,CAyQN,CAAC"}
@@ -144,6 +144,13 @@ export const createAPI = (config) => {
144
144
  queryEntries[key] = registeredEndpoint;
145
145
  router.register(registeredEndpoint);
146
146
  }
147
+ // Shared dataset client for all semantic endpoints, so metric and dataset
148
+ // entries share one result cache.
149
+ let sharedAnalytics;
150
+ const getAnalytics = (builderFactory) => {
151
+ sharedAnalytics ??= createDatasetClient({ queryBuilder: builderFactory });
152
+ return sharedAnalytics;
153
+ };
147
154
  // Process metrics — auto-generate POST endpoints
148
155
  if (config.metrics) {
149
156
  const metricsEntries = config.metrics;
@@ -153,7 +160,7 @@ export const createAPI = (config) => {
153
160
  'Pass the createQueryBuilder(config) return value as `queryBuilder`, ' +
154
161
  'or provide it via context as `context: () => ({ db: queryBuilder })`.');
155
162
  }
156
- const analytics = createDatasetClient({ queryBuilder: builderFactory });
163
+ const analytics = getAnalytics(builderFactory);
157
164
  for (const [name, entry] of Object.entries(metricsEntries)) {
158
165
  assertSemanticKeyAvailable(queryEntries, name, "metric");
159
166
  const metricEndpoint = createMetricEndpoint(name, entry, analytics, builderFactory);
@@ -178,7 +185,7 @@ export const createAPI = (config) => {
178
185
  }
179
186
  for (const [name, entry] of Object.entries(datasetEntries)) {
180
187
  assertSemanticKeyAvailable(queryEntries, `dataset:${name}`, "dataset");
181
- const datasetEndpoint = createDatasetEndpoint(name, entry, builderFactory);
188
+ const datasetEndpoint = createDatasetEndpoint(name, entry, getAnalytics(builderFactory), builderFactory);
182
189
  const datasetsPath = config.semanticPaths?.datasets ?? '/datasets';
183
190
  const routePath = normalizeRoutePath(`${datasetsPath}/${name}/query`);
184
191
  const registeredEndpoint = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/serve",
3
- "version": "0.6.1",
3
+ "version": "0.8.0",
4
4
  "description": "Declarative HTTP server for exposing hypequery analytics endpoints",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -21,7 +21,7 @@
21
21
  "openapi-typescript": "^7.13.0",
22
22
  "zod": "^3.23.8",
23
23
  "zod-to-json-schema": "^3.23.5",
24
- "@hypequery/datasets": "^0.6.0"
24
+ "@hypequery/datasets": "^0.8.0"
25
25
  },
26
26
  "peerDependencies": {
27
27
  "tsx": "^4.0.0"