@hypequery/serve 0.6.1 → 0.7.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/semantic/datasets/dataset-endpoint.d.ts +36 -3
- package/dist/semantic/datasets/dataset-endpoint.d.ts.map +1 -1
- package/dist/semantic/datasets/dataset-endpoint.js +19 -9
- package/dist/semantic/datasets/metric-endpoint.d.ts +33 -0
- package/dist/semantic/datasets/metric-endpoint.d.ts.map +1 -1
- package/dist/semantic/datasets/metric-endpoint.js +9 -1
- package/dist/server/create-api.d.ts.map +1 -1
- package/dist/server/create-api.js +9 -2
- package/package.json +2 -2
|
@@ -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
|
|
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;
|
|
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
|
|
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
|
|
78
|
-
const validation =
|
|
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
|
-
//
|
|
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
|
|
90
|
-
|
|
91
|
-
|
|
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;
|
|
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,
|
|
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 =
|
|
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.
|
|
3
|
+
"version": "0.7.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.
|
|
24
|
+
"@hypequery/datasets": "^0.7.0"
|
|
25
25
|
},
|
|
26
26
|
"peerDependencies": {
|
|
27
27
|
"tsx": "^4.0.0"
|