@hypequery/serve 0.2.1 → 0.3.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 (77) hide show
  1. package/README.md +138 -1
  2. package/dist/adapters/node.d.ts.map +1 -1
  3. package/dist/adapters/node.js +3 -5
  4. package/dist/adapters/standalone.d.ts +41 -0
  5. package/dist/adapters/standalone.d.ts.map +1 -0
  6. package/dist/adapters/standalone.js +46 -0
  7. package/dist/auth.d.ts +59 -83
  8. package/dist/auth.d.ts.map +1 -1
  9. package/dist/auth.js +136 -102
  10. package/dist/client-config.d.ts +3 -2
  11. package/dist/client-config.d.ts.map +1 -1
  12. package/dist/client-config.js +4 -2
  13. package/dist/errors.js +3 -0
  14. package/dist/index.d.ts +2 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +2 -0
  17. package/dist/openapi.js +1 -2
  18. package/dist/pipeline.d.ts.map +1 -1
  19. package/dist/pipeline.js +10 -22
  20. package/dist/query-logger.js +1 -3
  21. package/dist/rate-limit.js +4 -3
  22. package/dist/router.js +2 -1
  23. package/dist/semantic/datasets/dataset-endpoint.d.ts +85 -0
  24. package/dist/semantic/datasets/dataset-endpoint.d.ts.map +1 -0
  25. package/dist/semantic/datasets/dataset-endpoint.js +121 -0
  26. package/dist/semantic/datasets/index.d.ts +6 -0
  27. package/dist/semantic/datasets/index.d.ts.map +1 -0
  28. package/dist/semantic/datasets/index.js +5 -0
  29. package/dist/semantic/datasets/metric-endpoint.d.ts +82 -0
  30. package/dist/semantic/datasets/metric-endpoint.d.ts.map +1 -0
  31. package/dist/semantic/datasets/metric-endpoint.js +159 -0
  32. package/dist/semantic/datasets/utils/dataset-entry.d.ts +24 -0
  33. package/dist/semantic/datasets/utils/dataset-entry.d.ts.map +1 -0
  34. package/dist/semantic/datasets/utils/dataset-entry.js +15 -0
  35. package/dist/semantic/datasets/utils/dataset-query-metadata.d.ts +3 -0
  36. package/dist/semantic/datasets/utils/dataset-query-metadata.d.ts.map +1 -0
  37. package/dist/semantic/datasets/utils/dataset-query-metadata.js +12 -0
  38. package/dist/semantic/datasets/utils/semantic-input-schema.d.ts +107 -0
  39. package/dist/semantic/datasets/utils/semantic-input-schema.d.ts.map +1 -0
  40. package/dist/semantic/datasets/utils/semantic-input-schema.js +87 -0
  41. package/dist/semantic/index.d.ts +2 -0
  42. package/dist/semantic/index.d.ts.map +1 -0
  43. package/dist/semantic/index.js +1 -0
  44. package/dist/semantic/query-builder-context.d.ts +20 -0
  45. package/dist/semantic/query-builder-context.d.ts.map +1 -0
  46. package/dist/semantic/query-builder-context.js +66 -0
  47. package/dist/semantic/utils/tenant-runtime.d.ts +11 -0
  48. package/dist/semantic/utils/tenant-runtime.d.ts.map +1 -0
  49. package/dist/semantic/utils/tenant-runtime.js +48 -0
  50. package/dist/serve.d.ts +2 -2
  51. package/dist/serve.d.ts.map +1 -1
  52. package/dist/server/api-builder.d.ts +5 -0
  53. package/dist/server/api-builder.d.ts.map +1 -0
  54. package/dist/server/api-builder.js +76 -0
  55. package/dist/server/builder.d.ts.map +1 -1
  56. package/dist/server/builder.js +11 -1
  57. package/dist/server/create-api.d.ts +32 -0
  58. package/dist/server/create-api.d.ts.map +1 -0
  59. package/dist/server/create-api.js +211 -0
  60. package/dist/server/define-serve.d.ts +21 -2
  61. package/dist/server/define-serve.d.ts.map +1 -1
  62. package/dist/server/define-serve.js +53 -84
  63. package/dist/server/index.d.ts +2 -0
  64. package/dist/server/index.d.ts.map +1 -1
  65. package/dist/server/index.js +2 -0
  66. package/dist/server/init-serve.d.ts +1 -1
  67. package/dist/server/init-serve.d.ts.map +1 -1
  68. package/dist/server/init-serve.js +7 -2
  69. package/dist/type-tests/builder.test-d.d.ts +4 -0
  70. package/dist/type-tests/builder.test-d.d.ts.map +1 -1
  71. package/dist/type-tests/builder.test-d.js +16 -1
  72. package/dist/type-tests/semantic.test-d.d.ts +2 -0
  73. package/dist/type-tests/semantic.test-d.d.ts.map +1 -0
  74. package/dist/type-tests/semantic.test-d.js +59 -0
  75. package/dist/types.d.ts +227 -6
  76. package/dist/types.d.ts.map +1 -1
  77. package/package.json +5 -2
@@ -0,0 +1,121 @@
1
+ /**
2
+ * Converts a DatasetInstance into a standard ServeEndpoint for semantic queries.
3
+ *
4
+ * The generated endpoint is a POST handler that:
5
+ * - Accepts dimensions, measures, filters, orderBy, limit, offset, by
6
+ * - Validates requested dimensions/measures/filters against the dataset contract
7
+ * - Executes via QueryBuilderFactoryLike
8
+ * - Applies Serve-managed tenant filtering when configured
9
+ * - Returns { data } or { data, meta } based on headers
10
+ */
11
+ import { z } from 'zod';
12
+ import { runDatasetQuery, validateDatasetQuery, } from '@hypequery/datasets/internal';
13
+ import { ServeHttpError } from '../../errors.js';
14
+ import { resolveSemanticExecutionRuntime, resolveSemanticQueryBuilder, } from '../query-builder-context.js';
15
+ import { buildDatasetQueryDescription } from './utils/dataset-query-metadata.js';
16
+ import { resolveDatasetEntry } from './utils/dataset-entry.js';
17
+ import { buildDatasetInputSchema } from './utils/semantic-input-schema.js';
18
+ // ---------------------------------------------------------------------------
19
+ // Zod schemas for dataset query input / output
20
+ // ---------------------------------------------------------------------------
21
+ const datasetResultMetaSchema = z.object({
22
+ timingMs: z.number().optional(),
23
+ sql: z.string().optional(),
24
+ tenant: z.string().optional(),
25
+ rowCount: z.number().optional(),
26
+ pagination: z.object({
27
+ limit: z.number(),
28
+ offset: z.number(),
29
+ hasMore: z.boolean(),
30
+ }).optional(),
31
+ }).optional();
32
+ const datasetResultSchema = z.object({
33
+ data: z.array(z.record(z.unknown())),
34
+ meta: datasetResultMetaSchema,
35
+ });
36
+ // ---------------------------------------------------------------------------
37
+ // Factory
38
+ // ---------------------------------------------------------------------------
39
+ export function createDatasetEndpoint(name, entry, builderFactory) {
40
+ const resolved = resolveDatasetEntry(entry);
41
+ const ds = resolved.dataset;
42
+ const effectiveMaxLimit = resolved.maxLimit ?? ds.limits?.maxResultSize ?? 1000;
43
+ // Build a schema whose dimension/measure/filter fields are enumerated from
44
+ // this dataset's contract, so OpenAPI/docs and clients see the valid fields.
45
+ const datasetQueryInputSchema = buildDatasetInputSchema(ds);
46
+ const metadata = {
47
+ path: '', // filled by router.register
48
+ method: 'POST',
49
+ name: name,
50
+ summary: `Query the "${name}" semantic dataset`,
51
+ description: buildDatasetQueryDescription(ds, effectiveMaxLimit),
52
+ tags: ['datasets'],
53
+ requiresAuth: resolved.auth !== null ? undefined : false,
54
+ requiredRoles: resolved.requiredRoles,
55
+ requiredScopes: resolved.requiredScopes,
56
+ cacheTtlMs: resolved.cache,
57
+ visibility: 'public',
58
+ custom: {
59
+ usesServeTenantRuntime: true,
60
+ },
61
+ };
62
+ const handler = async (ctx) => {
63
+ const semanticContext = ctx;
64
+ const input = ctx.input ?? {};
65
+ const start = Date.now();
66
+ const runtime = resolveSemanticExecutionRuntime(semanticContext);
67
+ // Validate
68
+ const query = {
69
+ dimensions: input.dimensions,
70
+ measures: input.measures,
71
+ filters: input.filters,
72
+ orderBy: input.orderBy,
73
+ limit: Math.min(input.limit ?? effectiveMaxLimit, effectiveMaxLimit),
74
+ offset: input.offset,
75
+ by: input.by,
76
+ };
77
+ const executionContext = runtime ? { runtime } : undefined;
78
+ const validation = validateDatasetQuery(ds, query, executionContext);
79
+ if (!validation.valid) {
80
+ throw new ServeHttpError(400, 'VALIDATION_ERROR', validation.errors.join('; '));
81
+ }
82
+ // Build query
83
+ const runtimeBuilderFactory = resolveSemanticQueryBuilder(semanticContext, builderFactory);
84
+ if (ctx.tenantId) {
85
+ if (!runtime?.tenant || !ds.tenantKey) {
86
+ throw new ServeHttpError(500, 'INTERNAL_SERVER_ERROR', `Dataset endpoint "${name}" requires dataset tenantKey and serve tenant runtime when tenant isolation is enabled.`);
87
+ }
88
+ }
89
+ const result = await runDatasetQuery(ds, query, {
90
+ builderFactory: runtimeBuilderFactory,
91
+ context: executionContext,
92
+ });
93
+ const timingMs = Date.now() - start;
94
+ // Meta — opt in via the `includeMeta` input field or the x-include-meta header.
95
+ const includeMeta = input.includeMeta === true
96
+ || ctx.request?.headers?.['x-include-meta'] === 'true';
97
+ return {
98
+ data: result.data,
99
+ meta: includeMeta ? {
100
+ ...(result.meta ?? {}),
101
+ timingMs,
102
+ } : undefined,
103
+ };
104
+ };
105
+ return {
106
+ key: name,
107
+ method: 'POST',
108
+ inputSchema: datasetQueryInputSchema,
109
+ outputSchema: datasetResultSchema,
110
+ handler,
111
+ query: undefined,
112
+ middlewares: (resolved.middlewares ?? []),
113
+ auth: resolved.auth ?? null,
114
+ tenant: resolved.tenant,
115
+ metadata,
116
+ cacheTtlMs: resolved.cache ?? null,
117
+ defaultHeaders: undefined,
118
+ requiredRoles: resolved.requiredRoles,
119
+ requiredScopes: resolved.requiredScopes,
120
+ };
121
+ }
@@ -0,0 +1,6 @@
1
+ export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, asc, desc, filter, order, createDatasetRegistry, } from '@hypequery/datasets';
2
+ export type { FieldType, DimensionType, DimensionOptions, DimensionDefinition, MeasureOptions, MeasureDefinition, InferDimensionType, RelationshipKind, RelationshipDefinition, AggregationType, MeasureAggregation, AggregationSpec, FormulaExpr, DerivedMetricSpec, TimeGrain, MetricRef, BaseMetricRef, DerivedMetricRef, GrainedMetricRef, MetricContract, MetricFilter, MetricOrderBy, MetricQuery, DatasetQuery, MetricResultMeta, MetricResult, DatasetQueryResult, MetricHandle, ExecutionContext, SemanticExecutionRuntime, SemanticTenantRuntime, SemanticFilterDefinition, SemanticFiltersDefinition, DatasetConfig, DatasetLimits, DatasetInstance, BaseMetricConfig, DerivedMetricConfig, DatasetRegistryInstance, DatasetFieldNames, } from '@hypequery/datasets';
3
+ export { createMetricEndpoint } from './metric-endpoint.js';
4
+ export { createDatasetEndpoint } from './dataset-endpoint.js';
5
+ export type { DatasetEntry } from './dataset-endpoint.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/semantic/datasets/index.ts"],"names":[],"mappings":"AACA,OAAO,EACL,OAAO,EACP,SAAS,EACT,OAAO,EACP,SAAS,EACT,OAAO,EACP,MAAM,EACN,GAAG,EACH,KAAK,EACL,aAAa,EACb,GAAG,EACH,GAAG,EACH,GAAG,EACH,MAAM,EACN,QAAQ,EACR,QAAQ,EACR,GAAG,EACH,UAAU,EACV,QAAQ,EACR,KAAK,EACL,KAAK,EACL,IAAI,EACJ,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,EAAE,EACF,GAAG,EACH,MAAM,EACN,SAAS,EACT,OAAO,EACP,IAAI,EACJ,GAAG,EACH,IAAI,EACJ,MAAM,EACN,KAAK,EACL,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAE7B,YAAY,EACV,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,mBAAmB,EACnB,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,SAAS,EACT,SAAS,EACT,aAAa,EACb,gBAAgB,EAChB,gBAAgB,EAChB,cAAc,EACd,YAAY,EACZ,aAAa,EACb,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,YAAY,EACZ,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,EACrB,wBAAwB,EACxB,yBAAyB,EACzB,aAAa,EACb,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,mBAAmB,EACnB,uBAAuB,EACvB,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
@@ -0,0 +1,5 @@
1
+ // Re-export public datasets APIs for convenience
2
+ export { dataset, dimension, measure, belongsTo, hasMany, hasOne, sum, count, countDistinct, avg, min, max, divide, multiply, subtract, add, nullIfZero, coalesce, round, floor, ceil, eq, neq, gt, gte, lt, lte, inList, notInList, between, like, asc, desc, filter, order, createDatasetRegistry, } from '@hypequery/datasets';
3
+ // Serve-specific endpoint integration
4
+ export { createMetricEndpoint } from './metric-endpoint.js';
5
+ export { createDatasetEndpoint } from './dataset-endpoint.js';
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Converts a MetricRef into a standard ServeEndpoint.
3
+ *
4
+ * The generated endpoint is a POST handler that:
5
+ * - Validates dimensions/filters against the metric's contract
6
+ * - Calls DatasetClient.execute() with the parsed query + tenant context
7
+ * - Returns { data } or { data, meta } based on headers
8
+ */
9
+ import { z } from 'zod';
10
+ import type { AuthContext, MetricEntry, ServeEndpoint } from '../../types.js';
11
+ import type { DatasetClient, QueryBuilderFactoryLike } from '@hypequery/datasets';
12
+ declare const metricResultSchema: z.ZodObject<{
13
+ data: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
14
+ meta: z.ZodOptional<z.ZodObject<{
15
+ timingMs: z.ZodOptional<z.ZodNumber>;
16
+ sql: z.ZodOptional<z.ZodString>;
17
+ tenant: z.ZodOptional<z.ZodString>;
18
+ rowCount: z.ZodOptional<z.ZodNumber>;
19
+ pagination: z.ZodOptional<z.ZodObject<{
20
+ limit: z.ZodNumber;
21
+ offset: z.ZodNumber;
22
+ hasMore: z.ZodBoolean;
23
+ }, "strip", z.ZodTypeAny, {
24
+ limit: number;
25
+ offset: number;
26
+ hasMore: boolean;
27
+ }, {
28
+ limit: number;
29
+ offset: number;
30
+ hasMore: boolean;
31
+ }>>;
32
+ }, "strip", z.ZodTypeAny, {
33
+ tenant?: string | undefined;
34
+ timingMs?: number | undefined;
35
+ sql?: string | undefined;
36
+ rowCount?: number | undefined;
37
+ pagination?: {
38
+ limit: number;
39
+ offset: number;
40
+ hasMore: boolean;
41
+ } | undefined;
42
+ }, {
43
+ tenant?: string | undefined;
44
+ timingMs?: number | undefined;
45
+ sql?: string | undefined;
46
+ rowCount?: number | undefined;
47
+ pagination?: {
48
+ limit: number;
49
+ offset: number;
50
+ hasMore: boolean;
51
+ } | undefined;
52
+ }>>;
53
+ }, "strip", z.ZodTypeAny, {
54
+ data: Record<string, unknown>[];
55
+ meta?: {
56
+ tenant?: string | undefined;
57
+ timingMs?: number | undefined;
58
+ sql?: string | undefined;
59
+ rowCount?: number | undefined;
60
+ pagination?: {
61
+ limit: number;
62
+ offset: number;
63
+ hasMore: boolean;
64
+ } | undefined;
65
+ } | undefined;
66
+ }, {
67
+ data: Record<string, unknown>[];
68
+ meta?: {
69
+ tenant?: string | undefined;
70
+ timingMs?: number | undefined;
71
+ sql?: string | undefined;
72
+ rowCount?: number | undefined;
73
+ pagination?: {
74
+ limit: number;
75
+ offset: number;
76
+ hasMore: boolean;
77
+ } | undefined;
78
+ } | undefined;
79
+ }>;
80
+ export declare function createMetricEndpoint<TAuth extends AuthContext>(name: string, entry: MetricEntry<TAuth>, analytics: DatasetClient, defaultBuilderFactory: QueryBuilderFactoryLike): ServeEndpoint<z.ZodTypeAny, typeof metricResultSchema, any, TAuth, any>;
81
+ export {};
82
+ //# sourceMappingURL=metric-endpoint.d.ts.map
@@ -0,0 +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,EAIX,WAAW,EACX,aAAa,EAGd,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAsB,aAAa,EAAgC,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAwBpI,QAAA,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAGtB,CAAC;AAoDH,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"}
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Converts a MetricRef into a standard ServeEndpoint.
3
+ *
4
+ * The generated endpoint is a POST handler that:
5
+ * - Validates dimensions/filters against the metric's contract
6
+ * - Calls DatasetClient.execute() with the parsed query + tenant context
7
+ * - Returns { data } or { data, meta } based on headers
8
+ */
9
+ import { z } from 'zod';
10
+ import { ServeHttpError } from '../../errors.js';
11
+ import { resolveSemanticExecutionRuntime, resolveSemanticQueryBuilder, } from '../query-builder-context.js';
12
+ import { buildMetricInputSchema } from './utils/semantic-input-schema.js';
13
+ // ---------------------------------------------------------------------------
14
+ // Zod schemas for metric query input / output
15
+ // ---------------------------------------------------------------------------
16
+ const metricResultMetaSchema = z.object({
17
+ timingMs: z.number().optional(),
18
+ sql: z.string().optional(),
19
+ tenant: z.string().optional(),
20
+ rowCount: z.number().optional(),
21
+ pagination: z.object({
22
+ limit: z.number(),
23
+ offset: z.number(),
24
+ hasMore: z.boolean(),
25
+ }).optional(),
26
+ }).optional();
27
+ const metricResultSchema = z.object({
28
+ data: z.array(z.record(z.unknown())),
29
+ meta: metricResultMetaSchema,
30
+ });
31
+ function isMetricHandleEntry(entry) {
32
+ return (!!entry &&
33
+ typeof entry === 'object' &&
34
+ '__type' in entry &&
35
+ (entry.__type === 'metric_ref' || entry.__type === 'grained_metric_ref'));
36
+ }
37
+ function isMetricEntryOptions(entry) {
38
+ return !!entry && typeof entry === 'object' && 'metric' in entry;
39
+ }
40
+ function resolveMetricEntry(entry) {
41
+ if (isMetricHandleEntry(entry)) {
42
+ return { metric: entry };
43
+ }
44
+ if (isMetricEntryOptions(entry)) {
45
+ return entry;
46
+ }
47
+ throw new Error('Invalid metric entry.');
48
+ }
49
+ // ---------------------------------------------------------------------------
50
+ // Factory
51
+ // ---------------------------------------------------------------------------
52
+ export function createMetricEndpoint(name, entry, analytics, defaultBuilderFactory) {
53
+ const resolved = resolveMetricEntry(entry);
54
+ const metricRef = resolved.metric;
55
+ const contract = metricRef.contract();
56
+ // The metric's underlying dataset, used to enumerate valid query fields and
57
+ // page-size defaults.
58
+ const ds = (metricRef.__type === 'metric_ref' ? metricRef.dataset : metricRef.metric.dataset);
59
+ const metricQueryInputSchema = buildMetricInputSchema(ds, contract.name);
60
+ // Page-size cap, mirroring datasets: clamp (don't reject) and apply a default
61
+ // so a metric query is never unbounded.
62
+ const effectiveMaxLimit = resolved.maxLimit ?? ds.limits?.maxResultSize ?? 1000;
63
+ const metadata = {
64
+ path: '', // filled by router.register
65
+ method: 'POST',
66
+ name: contract.label ?? name,
67
+ summary: `Query the "${name}" metric`,
68
+ description: buildDescription(contract, effectiveMaxLimit),
69
+ tags: ['metrics'],
70
+ requiresAuth: resolved.auth !== null ? undefined : false,
71
+ requiredRoles: resolved.requiredRoles,
72
+ requiredScopes: resolved.requiredScopes,
73
+ cacheTtlMs: resolved.cache,
74
+ visibility: 'public',
75
+ custom: {
76
+ usesServeTenantRuntime: true,
77
+ },
78
+ };
79
+ const handler = async (ctx) => {
80
+ const semanticContext = ctx;
81
+ const input = ctx.input ?? {};
82
+ const runtime = resolveSemanticExecutionRuntime(semanticContext);
83
+ const runtimeBuilderFactory = resolveSemanticQueryBuilder(semanticContext, defaultBuilderFactory);
84
+ // Build the metric query
85
+ const query = {
86
+ dimensions: input.dimensions,
87
+ filters: input.filters,
88
+ orderBy: input.orderBy,
89
+ limit: Math.min(input.limit ?? effectiveMaxLimit, effectiveMaxLimit),
90
+ offset: input.offset,
91
+ by: input.by,
92
+ };
93
+ if (ctx.tenantId && !runtime?.tenant) {
94
+ throw new ServeHttpError(500, 'INTERNAL_SERVER_ERROR', `Metric endpoint "${name}" requires serve tenant runtime when tenant isolation is enabled.`);
95
+ }
96
+ const validationContext = ctx.tenantId && runtime?.tenant
97
+ ? {
98
+ runtime: {
99
+ tenant: runtime.tenant,
100
+ },
101
+ }
102
+ : undefined;
103
+ const validation = analytics.validate(metricRef, query, validationContext);
104
+ if (!validation.valid) {
105
+ throw new ServeHttpError(400, 'VALIDATION_ERROR', validation.errors.join('; '));
106
+ }
107
+ // Execute with tenant context
108
+ const result = await analytics.execute(metricRef, query, {
109
+ runtime: {
110
+ ...runtime,
111
+ builderFactory: runtimeBuilderFactory,
112
+ tenant: runtime?.tenant,
113
+ },
114
+ });
115
+ // Decide whether to include meta — `includeMeta` input field or x-include-meta header.
116
+ const includeMeta = input.includeMeta === true
117
+ || ctx.request?.headers?.['x-include-meta'] === 'true';
118
+ return {
119
+ data: result.data,
120
+ meta: includeMeta ? result.meta : undefined,
121
+ };
122
+ };
123
+ return {
124
+ key: name,
125
+ method: 'POST',
126
+ inputSchema: metricQueryInputSchema,
127
+ outputSchema: metricResultSchema,
128
+ handler,
129
+ query: undefined,
130
+ middlewares: (resolved.middlewares ?? []),
131
+ auth: resolved.auth ?? null,
132
+ tenant: resolved.tenant,
133
+ metadata,
134
+ cacheTtlMs: resolved.cache ?? null,
135
+ defaultHeaders: undefined,
136
+ requiredRoles: resolved.requiredRoles,
137
+ requiredScopes: resolved.requiredScopes,
138
+ };
139
+ }
140
+ function buildDescription(contract, maxLimit) {
141
+ const lines = [
142
+ contract.description ?? `${contract.name} metric on the ${contract.dataset} dataset.`,
143
+ '',
144
+ `**Type:** ${contract.kind}`,
145
+ `**Dataset:** ${contract.dataset}`,
146
+ `**Dimensions:** ${contract.dimensions.join(', ') || 'none'}`,
147
+ `**Max limit:** ${maxLimit}`,
148
+ ];
149
+ if (contract.grains.length > 0) {
150
+ lines.push(`**Time grains:** ${contract.grains.join(', ')}`);
151
+ }
152
+ if (contract.requires && contract.requires.length > 0) {
153
+ lines.push(`**Requires:** ${contract.requires.join(', ')}`);
154
+ }
155
+ if (contract.tenantScoped) {
156
+ lines.push('**Tenant scoped:** yes');
157
+ }
158
+ return lines.join('\n');
159
+ }
@@ -0,0 +1,24 @@
1
+ import type { AuthContext, AuthStrategy, ServeMiddleware, TenantConfigOverride } from '../../../types.js';
2
+ import type { AnyDatasetInstance } from '@hypequery/datasets';
3
+ export type DatasetEntry<TAuth extends AuthContext = AuthContext> = AnyDatasetInstance | {
4
+ dataset: AnyDatasetInstance;
5
+ auth?: AuthStrategy<TAuth> | null;
6
+ tenant?: TenantConfigOverride<TAuth>;
7
+ cache?: number | null;
8
+ requiredRoles?: string[];
9
+ requiredScopes?: string[];
10
+ /** Middleware applied to this dataset endpoint. */
11
+ middlewares?: ServeMiddleware<any, any, any, TAuth>[];
12
+ maxLimit?: number;
13
+ };
14
+ export declare function resolveDatasetEntry<TAuth extends AuthContext>(entry: DatasetEntry<TAuth>): {
15
+ dataset: AnyDatasetInstance;
16
+ auth?: AuthStrategy<TAuth> | null;
17
+ tenant?: TenantConfigOverride<TAuth>;
18
+ cache?: number | null;
19
+ requiredRoles?: string[];
20
+ requiredScopes?: string[];
21
+ middlewares?: ServeMiddleware<any, any, any, TAuth>[];
22
+ maxLimit?: number;
23
+ };
24
+ //# sourceMappingURL=dataset-entry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-entry.d.ts","sourceRoot":"","sources":["../../../../src/semantic/datasets/utils/dataset-entry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,WAAW,EACX,YAAY,EACZ,eAAe,EACf,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,MAAM,MAAM,YAAY,CAAC,KAAK,SAAS,WAAW,GAAG,WAAW,IAC5D,kBAAkB,GAClB;IACE,OAAO,EAAE,kBAAkB,CAAC;IAC5B,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,mDAAmD;IACnD,WAAW,CAAC,EAAE,eAAe,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,EAAE,CAAC;IACtD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAgBN,wBAAgB,mBAAmB,CAAC,KAAK,SAAS,WAAW,EAC3D,KAAK,EAAE,YAAY,CAAC,KAAK,CAAC,GACzB;IACD,OAAO,EAAE,kBAAkB,CAAC;IAC5B,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"}
@@ -0,0 +1,15 @@
1
+ function isDatasetInstance(entry) {
2
+ return !!entry && typeof entry === 'object' && '__type' in entry && entry.__type === 'dataset';
3
+ }
4
+ function isDatasetEntryOptions(entry) {
5
+ return !!entry && typeof entry === 'object' && 'dataset' in entry;
6
+ }
7
+ export function resolveDatasetEntry(entry) {
8
+ if (isDatasetInstance(entry)) {
9
+ return { dataset: entry };
10
+ }
11
+ if (isDatasetEntryOptions(entry)) {
12
+ return entry;
13
+ }
14
+ throw new Error('Invalid dataset entry.');
15
+ }
@@ -0,0 +1,3 @@
1
+ import type { AnyDatasetInstance } from '@hypequery/datasets';
2
+ export declare function buildDatasetQueryDescription(ds: AnyDatasetInstance, maxLimit: number): string;
3
+ //# sourceMappingURL=dataset-query-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dataset-query-metadata.d.ts","sourceRoot":"","sources":["../../../../src/semantic/datasets/utils/dataset-query-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAE9D,wBAAgB,4BAA4B,CAC1C,EAAE,EAAE,kBAAkB,EACtB,QAAQ,EAAE,MAAM,GACf,MAAM,CAYR"}
@@ -0,0 +1,12 @@
1
+ export function buildDatasetQueryDescription(ds, maxLimit) {
2
+ const dimensionNames = Object.keys(ds.dimensions);
3
+ const measureNames = Object.keys(ds.measures);
4
+ const lines = [
5
+ `Query the ${ds.name} semantic dataset (source: ${ds.source}).`,
6
+ '',
7
+ `**Dimensions:** ${dimensionNames.join(', ') || 'none'}`,
8
+ `**Measures:** ${measureNames.join(', ') || 'none'}`,
9
+ `**Max limit:** ${maxLimit}`,
10
+ ];
11
+ return lines.join('\n');
12
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Builds per-dataset / per-metric Zod input schemas whose dimension, measure,
3
+ * filter, and orderBy fields are constrained to the names the semantic
4
+ * validators accept. This upgrades the generated OpenAPI/docs from "array of
5
+ * arbitrary strings" to enumerated fields and enables typed client codegen.
6
+ *
7
+ * The enums are deliberately a *superset-safe* mirror of the runtime validators
8
+ * (`validateDatasetQueryInput` / the metric `validateQuery`): they never reject
9
+ * a field the validator would accept. Where a list would be empty (e.g. a
10
+ * dataset with no declared filters) we fall back to `z.string()` and let the
11
+ * validator produce the precise error, rather than emitting an empty enum.
12
+ */
13
+ import { z } from 'zod';
14
+ import type { AnyDatasetInstance } from '@hypequery/datasets';
15
+ /**
16
+ * Input schema for a dataset query endpoint, mirroring
17
+ * `validateDatasetQueryInput`.
18
+ */
19
+ export declare function buildDatasetInputSchema(ds: AnyDatasetInstance): z.ZodObject<{
20
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodTypeAny, "many">>;
21
+ measures: z.ZodOptional<z.ZodArray<z.ZodTypeAny, "many">>;
22
+ filters: z.ZodOptional<z.ZodArray<z.ZodTypeAny, "many">>;
23
+ orderBy: z.ZodOptional<z.ZodArray<z.ZodObject<{
24
+ field: z.ZodTypeAny;
25
+ direction: z.ZodEnum<["asc", "desc"]>;
26
+ }, "strip", z.ZodTypeAny, {
27
+ direction: "asc" | "desc";
28
+ field?: any;
29
+ }, {
30
+ direction: "asc" | "desc";
31
+ field?: any;
32
+ }>, "many">>;
33
+ limit: z.ZodOptional<z.ZodNumber>;
34
+ offset: z.ZodOptional<z.ZodNumber>;
35
+ by: z.ZodOptional<z.ZodEnum<["day", "week", "month", "quarter", "year"]>>;
36
+ includeMeta: z.ZodOptional<z.ZodBoolean>;
37
+ }, "strict", z.ZodTypeAny, {
38
+ dimensions?: any[] | undefined;
39
+ measures?: any[] | undefined;
40
+ filters?: any[] | undefined;
41
+ orderBy?: {
42
+ direction: "asc" | "desc";
43
+ field?: any;
44
+ }[] | undefined;
45
+ limit?: number | undefined;
46
+ offset?: number | undefined;
47
+ by?: "day" | "week" | "month" | "quarter" | "year" | undefined;
48
+ includeMeta?: boolean | undefined;
49
+ }, {
50
+ dimensions?: any[] | undefined;
51
+ measures?: any[] | undefined;
52
+ filters?: any[] | undefined;
53
+ orderBy?: {
54
+ direction: "asc" | "desc";
55
+ field?: any;
56
+ }[] | undefined;
57
+ limit?: number | undefined;
58
+ offset?: number | undefined;
59
+ by?: "day" | "week" | "month" | "quarter" | "year" | undefined;
60
+ includeMeta?: boolean | undefined;
61
+ }>;
62
+ /**
63
+ * Input schema for a metric query endpoint, mirroring the metric `validateQuery`.
64
+ * Metrics select a single value, so there is no `measures` field; `orderBy` may
65
+ * reference the metric's own output column (`metricName`).
66
+ */
67
+ export declare function buildMetricInputSchema(ds: AnyDatasetInstance, metricName: string): z.ZodObject<{
68
+ dimensions: z.ZodOptional<z.ZodArray<z.ZodTypeAny, "many">>;
69
+ filters: z.ZodOptional<z.ZodArray<z.ZodTypeAny, "many">>;
70
+ orderBy: z.ZodOptional<z.ZodArray<z.ZodObject<{
71
+ field: z.ZodTypeAny;
72
+ direction: z.ZodEnum<["asc", "desc"]>;
73
+ }, "strip", z.ZodTypeAny, {
74
+ direction: "asc" | "desc";
75
+ field?: any;
76
+ }, {
77
+ direction: "asc" | "desc";
78
+ field?: any;
79
+ }>, "many">>;
80
+ limit: z.ZodOptional<z.ZodNumber>;
81
+ offset: z.ZodOptional<z.ZodNumber>;
82
+ by: z.ZodOptional<z.ZodEnum<["day", "week", "month", "quarter", "year"]>>;
83
+ includeMeta: z.ZodOptional<z.ZodBoolean>;
84
+ }, "strip", z.ZodTypeAny, {
85
+ dimensions?: any[] | undefined;
86
+ filters?: any[] | undefined;
87
+ orderBy?: {
88
+ direction: "asc" | "desc";
89
+ field?: any;
90
+ }[] | undefined;
91
+ limit?: number | undefined;
92
+ offset?: number | undefined;
93
+ by?: "day" | "week" | "month" | "quarter" | "year" | undefined;
94
+ includeMeta?: boolean | undefined;
95
+ }, {
96
+ dimensions?: any[] | undefined;
97
+ filters?: any[] | undefined;
98
+ orderBy?: {
99
+ direction: "asc" | "desc";
100
+ field?: any;
101
+ }[] | undefined;
102
+ limit?: number | undefined;
103
+ offset?: number | undefined;
104
+ by?: "day" | "week" | "month" | "quarter" | "year" | undefined;
105
+ includeMeta?: boolean | undefined;
106
+ }>;
107
+ //# sourceMappingURL=semantic-input-schema.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"semantic-input-schema.d.ts","sourceRoot":"","sources":["../../../../src/semantic/datasets/utils/semantic-input-schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,qBAAqB,CAAC;AAqC9D;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmB7D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiBhF"}
@@ -0,0 +1,87 @@
1
+ /**
2
+ * Builds per-dataset / per-metric Zod input schemas whose dimension, measure,
3
+ * filter, and orderBy fields are constrained to the names the semantic
4
+ * validators accept. This upgrades the generated OpenAPI/docs from "array of
5
+ * arbitrary strings" to enumerated fields and enables typed client codegen.
6
+ *
7
+ * The enums are deliberately a *superset-safe* mirror of the runtime validators
8
+ * (`validateDatasetQueryInput` / the metric `validateQuery`): they never reject
9
+ * a field the validator would accept. Where a list would be empty (e.g. a
10
+ * dataset with no declared filters) we fall back to `z.string()` and let the
11
+ * validator produce the precise error, rather than emitting an empty enum.
12
+ */
13
+ import { z } from 'zod';
14
+ const OPERATORS = [
15
+ 'eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'in', 'notIn', 'between', 'like',
16
+ ];
17
+ const GRAINS = ['day', 'week', 'month', 'quarter', 'year'];
18
+ /** An enum over the given field names, or a plain string when none are known. */
19
+ function fieldEnum(values) {
20
+ const unique = Array.from(new Set(values));
21
+ return unique.length > 0
22
+ ? z.enum(unique)
23
+ : z.string();
24
+ }
25
+ function filterSchema(fieldNames) {
26
+ return z.object({
27
+ field: fieldEnum(fieldNames),
28
+ operator: z.enum(OPERATORS),
29
+ value: z.unknown(),
30
+ });
31
+ }
32
+ function orderBySchema(fieldNames) {
33
+ return z.object({
34
+ field: fieldEnum(fieldNames),
35
+ direction: z.enum(['asc', 'desc']),
36
+ });
37
+ }
38
+ /** Apply a `.max()` bound only when the dataset declares one. */
39
+ function boundedArray(item, max) {
40
+ const arr = z.array(item);
41
+ return (max != null ? arr.max(max) : arr).optional();
42
+ }
43
+ /**
44
+ * Input schema for a dataset query endpoint, mirroring
45
+ * `validateDatasetQueryInput`.
46
+ */
47
+ export function buildDatasetInputSchema(ds) {
48
+ const dimensionNames = Object.keys(ds.dimensions);
49
+ const measureNames = Object.keys(ds.measures);
50
+ // Dataset filters are keyed by filter-definition name (no dimension fallback).
51
+ const filterNames = Object.keys(ds.filters);
52
+ // orderBy is query-dependent at runtime; the static superset is every
53
+ // dimension/measure plus the synthetic `period` column when grained.
54
+ const orderableNames = [...dimensionNames, ...measureNames, 'period'];
55
+ return z.object({
56
+ dimensions: boundedArray(fieldEnum(dimensionNames), ds.limits?.maxDimensions),
57
+ measures: boundedArray(fieldEnum(measureNames), ds.limits?.maxMeasures),
58
+ filters: boundedArray(filterSchema(filterNames), ds.limits?.maxFilters),
59
+ orderBy: z.array(orderBySchema(orderableNames)).optional(),
60
+ limit: z.number().int().positive().optional(),
61
+ offset: z.number().int().nonnegative().optional(),
62
+ by: z.enum(GRAINS).optional(),
63
+ includeMeta: z.boolean().optional(),
64
+ }).strict();
65
+ }
66
+ /**
67
+ * Input schema for a metric query endpoint, mirroring the metric `validateQuery`.
68
+ * Metrics select a single value, so there is no `measures` field; `orderBy` may
69
+ * reference the metric's own output column (`metricName`).
70
+ */
71
+ export function buildMetricInputSchema(ds, metricName) {
72
+ const dimensionNames = Object.keys(ds.dimensions);
73
+ // Metric validator falls back to dimension names when no filters are declared.
74
+ const filterNames = Object.keys(ds.filters).length > 0
75
+ ? Object.keys(ds.filters)
76
+ : dimensionNames;
77
+ const orderableNames = [...dimensionNames, metricName, 'period'];
78
+ return z.object({
79
+ dimensions: boundedArray(fieldEnum(dimensionNames), ds.limits?.maxDimensions),
80
+ filters: boundedArray(filterSchema(filterNames), ds.limits?.maxFilters),
81
+ orderBy: z.array(orderBySchema(orderableNames)).optional(),
82
+ limit: z.number().int().positive().optional(),
83
+ offset: z.number().int().nonnegative().optional(),
84
+ by: z.enum(GRAINS).optional(),
85
+ includeMeta: z.boolean().optional(),
86
+ });
87
+ }
@@ -0,0 +1,2 @@
1
+ export * from './datasets/index.js';
2
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semantic/index.ts"],"names":[],"mappings":"AAAA,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1 @@
1
+ export * from './datasets/index.js';