@hypequery/serve 0.16.1 → 0.17.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.
@@ -0,0 +1,44 @@
1
+ /**
2
+ * Deploy-time diagnostics for managed (Cloud) execution.
3
+ *
4
+ * A deployment contract carries policy, not mechanism: an `AuthStrategy` is
5
+ * reduced to `{ kind, roles, scopes }`, a `TenantConfig.extract` to a column
6
+ * name, and middlewares, hooks, and context factories are not carried at all.
7
+ * That is deliberate — Cloud re-implements those concerns itself — but it means
8
+ * a Serve app can behave differently once deployed, and silently.
9
+ *
10
+ * The dangerous case is tenancy. `resolveTenantFilterColumn` returns the
11
+ * dataset's `tenantKey`, and the planner applies the tenant predicate only when
12
+ * a column resolves. A dataset with no `tenantKey` exposed under a config that
13
+ * requires a tenant will demand a tenant and filter nothing, so every tenant
14
+ * reads every row. Locally a middleware or resolver may cover that gap; in
15
+ * Cloud there is no customer code left to do it.
16
+ *
17
+ * These checks run when the deployment contract is built, so they apply to
18
+ * every consumer rather than only the CLI.
19
+ */
20
+ import type { ServeConfig } from './types.js';
21
+ export type CloudCompatibilitySeverity = 'error' | 'warning';
22
+ export type CloudCompatibilityCode = 'HQ_CLOUD_TENANT_NOT_ENFORCEABLE' | 'HQ_CLOUD_MIDDLEWARE_DROPPED' | 'HQ_CLOUD_HOOKS_DROPPED' | 'HQ_CLOUD_CONTEXT_DROPPED' | 'HQ_CLOUD_AUTH_WITHOUT_ROLES';
23
+ export interface CloudCompatibilityDiagnostic {
24
+ readonly severity: CloudCompatibilitySeverity;
25
+ readonly code: CloudCompatibilityCode;
26
+ /** The endpoint, dataset, or config key the finding applies to. */
27
+ readonly subject: string;
28
+ readonly message: string;
29
+ /** What the author should do instead. */
30
+ readonly remedy: string;
31
+ }
32
+ /**
33
+ * Reports how a Serve config will differ once executed by Cloud.
34
+ *
35
+ * Pure and side-effect free so it can be run for reporting without building a
36
+ * contract.
37
+ */
38
+ export declare function analyzeCloudCompatibility(config: ServeConfig<any, any, any, any, any>): readonly CloudCompatibilityDiagnostic[];
39
+ /**
40
+ * Formats diagnostics for a thrown error. Errors are listed first because they
41
+ * are what blocks the deployment.
42
+ */
43
+ export declare function formatCloudCompatibilityDiagnostics(diagnostics: readonly CloudCompatibilityDiagnostic[]): string;
44
+ //# sourceMappingURL=cloud-compatibility.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloud-compatibility.d.ts","sourceRoot":"","sources":["../src/cloud-compatibility.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAA0C,WAAW,EAAE,MAAM,YAAY,CAAC;AAKtF,MAAM,MAAM,0BAA0B,GAAG,OAAO,GAAG,SAAS,CAAC;AAE7D,MAAM,MAAM,sBAAsB,GAC9B,iCAAiC,GACjC,6BAA6B,GAC7B,wBAAwB,GACxB,0BAA0B,GAC1B,6BAA6B,CAAC;AAElC,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,QAAQ,EAAE,0BAA0B,CAAC;IAC9C,QAAQ,CAAC,IAAI,EAAE,sBAAsB,CAAC;IACtC,mEAAmE;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yCAAyC;IACzC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAoDD;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAC3C,SAAS,4BAA4B,EAAE,CAiJzC;AAQD;;;GAGG;AACH,wBAAgB,mCAAmC,CACjD,WAAW,EAAE,SAAS,4BAA4B,EAAE,GACnD,MAAM,CAIR"}
@@ -0,0 +1,188 @@
1
+ /**
2
+ * Deploy-time diagnostics for managed (Cloud) execution.
3
+ *
4
+ * A deployment contract carries policy, not mechanism: an `AuthStrategy` is
5
+ * reduced to `{ kind, roles, scopes }`, a `TenantConfig.extract` to a column
6
+ * name, and middlewares, hooks, and context factories are not carried at all.
7
+ * That is deliberate — Cloud re-implements those concerns itself — but it means
8
+ * a Serve app can behave differently once deployed, and silently.
9
+ *
10
+ * The dangerous case is tenancy. `resolveTenantFilterColumn` returns the
11
+ * dataset's `tenantKey`, and the planner applies the tenant predicate only when
12
+ * a column resolves. A dataset with no `tenantKey` exposed under a config that
13
+ * requires a tenant will demand a tenant and filter nothing, so every tenant
14
+ * reads every row. Locally a middleware or resolver may cover that gap; in
15
+ * Cloud there is no customer code left to do it.
16
+ *
17
+ * These checks run when the deployment contract is built, so they apply to
18
+ * every consumer rather than only the CLI.
19
+ */
20
+ import { resolveDatasetEntry } from './semantic/datasets/utils/dataset-entry.js';
21
+ import { resolveLocalAuthRequirement } from './auth-requirement.js';
22
+ import { resolveMetricEntry } from './semantic/datasets/metric-endpoint.js';
23
+ function tenantIsExpected(local, global) {
24
+ if (local === undefined && global === undefined)
25
+ return false;
26
+ const effective = { ...(global ?? {}), ...(local ?? {}) };
27
+ return effective.required !== false;
28
+ }
29
+ function hasAuth(auth) {
30
+ return Array.isArray(auth) ? auth.length > 0 : auth !== undefined && auth !== null;
31
+ }
32
+ function declaresRolesOrScopes(entry) {
33
+ return (entry.requiredRoles?.length ?? 0) > 0 || (entry.requiredScopes?.length ?? 0) > 0;
34
+ }
35
+ /**
36
+ * True when an endpoint ends up authenticated but declares nothing Cloud can
37
+ * enforce. Mirrors `accessPolicy` in the protocol adapter, including the local
38
+ * opt-out: an endpoint with `auth: null` or `requiresAuth: false` resolves to
39
+ * public and is not a downgrade.
40
+ */
41
+ function isAuthenticatedWithoutRoles(local, globalAuth) {
42
+ if (declaresRolesOrScopes(local))
43
+ return false;
44
+ return (resolveLocalAuthRequirement(local) ?? hasAuth(globalAuth)) === true;
45
+ }
46
+ /**
47
+ * Reports how a Serve config will differ once executed by Cloud.
48
+ *
49
+ * Pure and side-effect free so it can be run for reporting without building a
50
+ * contract.
51
+ */
52
+ export function analyzeCloudCompatibility(config) {
53
+ const serveConfig = config;
54
+ const diagnostics = [];
55
+ // Semantic endpoints have no customer code in Cloud, so an unenforceable
56
+ // tenant requirement silently becomes no isolation at all.
57
+ const semanticTargets = [
58
+ ...Object.entries(serveConfig.datasets ?? {}).map(([name, entry]) => ({
59
+ label: `datasets.${name}`,
60
+ entry,
61
+ })),
62
+ ...Object.entries(serveConfig.metrics ?? {}).map(([name, entry]) => ({
63
+ label: `metrics.${name}`,
64
+ entry,
65
+ })),
66
+ ];
67
+ for (const { label, entry } of semanticTargets) {
68
+ const resolved = label.startsWith('datasets.')
69
+ ? resolveDatasetEntry(entry)
70
+ : resolveMetricEntry(entry);
71
+ const dataset = label.startsWith('datasets.')
72
+ ? resolved.dataset
73
+ : datasetOfMetric(resolved);
74
+ const local = resolved.tenant;
75
+ if (!tenantIsExpected(local, serveConfig.tenant))
76
+ continue;
77
+ if (dataset?.tenantKey)
78
+ continue;
79
+ diagnostics.push({
80
+ severity: 'error',
81
+ code: 'HQ_CLOUD_TENANT_NOT_ENFORCEABLE',
82
+ subject: label,
83
+ message: `Tenant isolation is required for "${label}" but dataset `
84
+ + `"${dataset?.name ?? 'unknown'}" declares no tenantKey. Cloud resolves the `
85
+ + 'tenant filter column from the dataset, so it would accept a tenant and '
86
+ + 'return every tenant\'s rows.',
87
+ remedy: `Add tenantKey to the "${dataset?.name ?? ''}" dataset, or set `
88
+ + 'tenant.required to false if this data is genuinely shared.',
89
+ });
90
+ }
91
+ // Everything below is dropped by the protocol adapter.
92
+ const globalMiddlewares = serveConfig.middlewares ?? [];
93
+ if (globalMiddlewares.length > 0) {
94
+ diagnostics.push({
95
+ severity: 'error',
96
+ code: 'HQ_CLOUD_MIDDLEWARE_DROPPED',
97
+ subject: 'middlewares',
98
+ message: `${globalMiddlewares.length} global middleware(s) are not carried in the `
99
+ + 'deployment contract and will not run in Cloud. Middleware that performs '
100
+ + 'authentication or filtering would leave endpoints less protected than '
101
+ + 'they are locally.',
102
+ remedy: 'Express the requirement declaratively with requiresAuth, requiredRoles, '
103
+ + 'requiredScopes, or tenant config, or keep this app self-hosted.',
104
+ });
105
+ }
106
+ const queryEntries = Object.entries(serveConfig.queries ?? {});
107
+ for (const [name, query] of queryEntries) {
108
+ if ((query?.middlewares?.length ?? 0) === 0)
109
+ continue;
110
+ diagnostics.push({
111
+ severity: 'error',
112
+ code: 'HQ_CLOUD_MIDDLEWARE_DROPPED',
113
+ subject: `queries.${name}`,
114
+ message: `Middleware on query "${name}" is not carried in the deployment contract `
115
+ + 'and will not run in Cloud.',
116
+ remedy: 'Express the requirement declaratively on the query, or move the logic into '
117
+ + 'the query resolver itself.',
118
+ });
119
+ }
120
+ if (serveConfig.hooks !== undefined) {
121
+ diagnostics.push({
122
+ severity: 'warning',
123
+ code: 'HQ_CLOUD_HOOKS_DROPPED',
124
+ subject: 'hooks',
125
+ message: 'Lifecycle hooks do not run in Cloud.',
126
+ remedy: 'Cloud records its own request log for deployed endpoints; remove the hooks '
127
+ + 'or keep them for local runs only.',
128
+ });
129
+ }
130
+ if (serveConfig.context !== undefined) {
131
+ diagnostics.push({
132
+ severity: 'warning',
133
+ code: 'HQ_CLOUD_CONTEXT_DROPPED',
134
+ subject: 'context',
135
+ message: 'The context factory does not run in Cloud, so values it provides will be '
136
+ + 'absent from query resolvers.',
137
+ remedy: 'Derive those values inside the resolver, or pass them as query input.',
138
+ });
139
+ }
140
+ {
141
+ // Custom queries undergo the same reduction as semantic endpoints: the
142
+ // adapter calls endpointPolicy for all three, so a query with a strategy
143
+ // but no declared roles is downgraded exactly the same way.
144
+ const endpoints = [
145
+ ...Object.entries(serveConfig.datasets ?? {}).map(([name, entry]) => [
146
+ `datasets.${name}`,
147
+ resolveDatasetEntry(entry),
148
+ ]),
149
+ ...Object.entries(serveConfig.metrics ?? {}).map(([name, entry]) => [
150
+ `metrics.${name}`,
151
+ resolveMetricEntry(entry),
152
+ ]),
153
+ ...Object.entries(serveConfig.queries ?? {}).map(([name, entry]) => [
154
+ `queries.${name}`,
155
+ (entry ?? {}),
156
+ ]),
157
+ ];
158
+ const undeclared = endpoints.filter(([, resolved]) => isAuthenticatedWithoutRoles(resolved, serveConfig.auth));
159
+ for (const [label] of undeclared) {
160
+ diagnostics.push({
161
+ severity: 'warning',
162
+ code: 'HQ_CLOUD_AUTH_WITHOUT_ROLES',
163
+ subject: label,
164
+ message: `"${label}" is authenticated but declares no roles or scopes. The auth `
165
+ + 'strategy itself is not carried, so Cloud accepts any valid credential '
166
+ + 'for this endpoint even if the local strategy is stricter.',
167
+ remedy: 'Declare requiredRoles or requiredScopes so Cloud can enforce the same '
168
+ + 'restriction.',
169
+ });
170
+ }
171
+ }
172
+ return diagnostics;
173
+ }
174
+ function datasetOfMetric(resolved) {
175
+ const metric = resolved.metric;
176
+ if (!metric)
177
+ return undefined;
178
+ return metric.__type === 'grained_metric_ref' ? metric.metric?.dataset : metric.dataset;
179
+ }
180
+ /**
181
+ * Formats diagnostics for a thrown error. Errors are listed first because they
182
+ * are what blocks the deployment.
183
+ */
184
+ export function formatCloudCompatibilityDiagnostics(diagnostics) {
185
+ return diagnostics
186
+ .map(d => ` [${d.severity}] ${d.code} (${d.subject})\n ${d.message}\n → ${d.remedy}`)
187
+ .join('\n\n');
188
+ }
package/dist/index.d.ts CHANGED
@@ -20,9 +20,13 @@ export { createCacheObservability, detectBuilderCache } from "./cache-observabil
20
20
  export type { CacheObservability, CacheLayerStats, BuilderCacheLike } from "./cache-observability.js";
21
21
  export { buildProtocolDeploymentContract } from './protocol-adapter.js';
22
22
  export type { BuildProtocolDeploymentOptions } from './protocol-adapter.js';
23
+ export { analyzeCloudCompatibility, formatCloudCompatibilityDiagnostics, } from './cloud-compatibility.js';
24
+ export type { CloudCompatibilityCode, CloudCompatibilityDiagnostic, CloudCompatibilitySeverity, } from './cloud-compatibility.js';
23
25
  export { ProtocolSchemaAdapterError, zodToProtocolSchema } from './protocol-schema-adapter.js';
24
26
  /** @deprecated Import from `@hypequery/serve/dev` instead. */
25
27
  export { serveDev } from "./dev.js";
26
28
  export * from "./serve.js";
27
29
  export * from "./semantic/index.js";
30
+ export { readServeMcpSource } from './server/mcp-source.js';
31
+ export type { ServeMcpSource } from './server/mcp-source.js';
28
32
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACtG,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,8DAA8D;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC;AAC9B,cAAc,cAAc,CAAC;AAC7B,cAAc,cAAc,CAAC;AAC7B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC;AAC1B,cAAc,aAAa,CAAC;AAC5B,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,sBAAsB,CAAC;AACrC,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AACtF,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AACnE,OAAO,EAAE,wBAAwB,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AACxF,YAAY,EAAE,kBAAkB,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACtG,OAAO,EAAE,+BAA+B,EAAE,MAAM,uBAAuB,CAAC;AACxE,YAAY,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EACL,yBAAyB,EACzB,mCAAmC,GACpC,MAAM,0BAA0B,CAAC;AAClC,YAAY,EACV,sBAAsB,EACtB,4BAA4B,EAC5B,0BAA0B,GAC3B,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,0BAA0B,EAAE,mBAAmB,EAAE,MAAM,8BAA8B,CAAC;AAC/F,8DAA8D;AAC9D,OAAO,EAAE,QAAQ,EAAE,MAAM,UAAU,CAAC;AACpC,cAAc,YAAY,CAAC;AAC3B,cAAc,qBAAqB,CAAC;AAGpC,OAAO,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC5D,YAAY,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC"}
package/dist/index.js CHANGED
@@ -17,8 +17,11 @@ export * from "./adapters/vercel.js";
17
17
  export { startServer, toNodeHandler, toFetchHandler } from "./adapters/standalone.js";
18
18
  export { createCacheObservability, detectBuilderCache } from "./cache-observability.js";
19
19
  export { buildProtocolDeploymentContract } from './protocol-adapter.js';
20
+ export { analyzeCloudCompatibility, formatCloudCompatibilityDiagnostics, } from './cloud-compatibility.js';
20
21
  export { ProtocolSchemaAdapterError, zodToProtocolSchema } from './protocol-schema-adapter.js';
21
22
  /** @deprecated Import from `@hypequery/serve/dev` instead. */
22
23
  export { serveDev } from "./dev.js";
23
24
  export * from "./serve.js";
24
25
  export * from "./semantic/index.js";
26
+ // MCP tooling seam: lets `hypequery mcp` reuse this entrypoint's datasets.
27
+ export { readServeMcpSource } from './server/mcp-source.js';
@@ -1,4 +1,5 @@
1
1
  import { type ProtocolDeploymentContract, type ProtocolQueryImplementation, type ProtocolRuntimeArtifact, type ProtocolSchema } from '@hypequery/protocol';
2
+ import { type CloudCompatibilityDiagnostic } from './cloud-compatibility.js';
2
3
  import type { ServeConfig } from './types.js';
3
4
  export interface BuildProtocolDeploymentOptions {
4
5
  /** Runtime artifact used for Serve callbacks without an explicit implementation override. */
@@ -12,6 +13,16 @@ export interface BuildProtocolDeploymentOptions {
12
13
  readonly input?: ProtocolSchema;
13
14
  readonly output?: ProtocolSchema;
14
15
  }>>;
16
+ /**
17
+ * Receives every managed-execution diagnostic, including ones that do not
18
+ * block the build. Without it, warnings are discarded and only errors surface.
19
+ */
20
+ readonly onCloudDiagnostic?: (diagnostic: CloudCompatibilityDiagnostic) => void;
21
+ /**
22
+ * Downgrades managed-execution errors to warnings. The author is asserting
23
+ * they know the deployed behaviour differs from local.
24
+ */
25
+ readonly allowUnsupportedConfig?: boolean;
15
26
  }
16
27
  /**
17
28
  * Converts an existing Serve configuration and its Dataset/metric definitions
@@ -1 +1 @@
1
- {"version":3,"file":"protocol-adapter.d.ts","sourceRoot":"","sources":["../src/protocol-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAG/B,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,KAAK,EAOV,WAAW,EAGZ,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,8BAA8B;IAC7C,6FAA6F;IAC7F,QAAQ,CAAC,eAAe,CAAC,EAAE,uBAAuB,GAAG;QACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KACpC,CAAC;IACF,mFAAmF;IACnF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC,CAAC;IACtF,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;KAClC,CAAC,CAAC,CAAC;CACL;AA2JD;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC5C,OAAO,GAAE,8BAAmC,GAC3C,0BAA0B,CAkF5B"}
1
+ {"version":3,"file":"protocol-adapter.d.ts","sourceRoot":"","sources":["../src/protocol-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,0BAA0B,EAG/B,KAAK,2BAA2B,EAChC,KAAK,uBAAuB,EAC5B,KAAK,cAAc,EACpB,MAAM,qBAAqB,CAAC;AAM7B,OAAO,EAGL,KAAK,4BAA4B,EAClC,MAAM,0BAA0B,CAAC;AAClC,OAAO,KAAK,EAOV,WAAW,EAGZ,MAAM,YAAY,CAAC;AAMpB,MAAM,WAAW,8BAA8B;IAC7C,6FAA6F;IAC7F,QAAQ,CAAC,eAAe,CAAC,EAAE,uBAAuB,GAAG;QACnD,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;KACpC,CAAC;IACF,mFAAmF;IACnF,QAAQ,CAAC,oBAAoB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,2BAA2B,CAAC,CAAC,CAAC;IACtF,gFAAgF;IAChF,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE;QAC9C,QAAQ,CAAC,KAAK,CAAC,EAAE,cAAc,CAAC;QAChC,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;KAClC,CAAC,CAAC,CAAC;IACJ;;;OAGG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,UAAU,EAAE,4BAA4B,KAAK,IAAI,CAAC;IAChF;;;OAGG;IACH,QAAQ,CAAC,sBAAsB,CAAC,EAAE,OAAO,CAAC;CAC3C;AA6KD;;;GAGG;AACH,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,WAAW,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC5C,OAAO,GAAE,8BAAmC,GAC3C,0BAA0B,CAmF5B"}
@@ -1,9 +1,25 @@
1
1
  import { validateProtocolDeploymentContract, } from '@hypequery/protocol';
2
2
  import { buildProtocolDatasetContract, } from '@hypequery/datasets';
3
+ import { analyzeCloudCompatibility, formatCloudCompatibilityDiagnostics, } from './cloud-compatibility.js';
3
4
  import { resolveDatasetEntry } from './semantic/datasets/utils/dataset-entry.js';
4
5
  import { resolveMetricEntry } from './semantic/datasets/metric-endpoint.js';
5
6
  import { zodToProtocolSchema } from './protocol-schema-adapter.js';
6
7
  import { resolveLocalAuthRequirement } from './auth-requirement.js';
8
+ function reportCloudCompatibility(config, options) {
9
+ const diagnostics = analyzeCloudCompatibility(config);
10
+ if (diagnostics.length === 0)
11
+ return;
12
+ for (const diagnostic of diagnostics)
13
+ options.onCloudDiagnostic?.(diagnostic);
14
+ if (options.allowUnsupportedConfig)
15
+ return;
16
+ const blocking = diagnostics.filter(diagnostic => diagnostic.severity === 'error');
17
+ if (blocking.length === 0)
18
+ return;
19
+ throw new Error('This Serve configuration would behave differently under managed execution:\n\n'
20
+ + `${formatCloudCompatibilityDiagnostics(blocking)}\n\n`
21
+ + 'Resolve these, or pass allowUnsupportedConfig to deploy anyway.');
22
+ }
7
23
  function normalizePath(...parts) {
8
24
  const joined = parts
9
25
  .filter(Boolean)
@@ -110,6 +126,7 @@ function runtimeArtifacts(implementations, configured) {
110
126
  * into the strict, immutable protocol deployment contract.
111
127
  */
112
128
  export function buildProtocolDeploymentContract(config, options = {}) {
129
+ reportCloudCompatibility(config, options);
113
130
  const serveConfig = config;
114
131
  const basePath = serveConfig.basePath ?? '/api/analytics';
115
132
  const datasetsPath = serveConfig.semanticPaths?.datasets ?? '/datasets';
@@ -48,11 +48,6 @@ declare const datasetResultSchema: z.ZodObject<{
48
48
  }>>;
49
49
  }, "strip", z.ZodTypeAny, {
50
50
  tenant?: string | undefined;
51
- cache?: {
52
- hit: boolean;
53
- ageMs?: number | undefined;
54
- stale?: boolean | undefined;
55
- } | undefined;
56
51
  timingMs?: number | undefined;
57
52
  sql?: string | undefined;
58
53
  rowCount?: number | undefined;
@@ -61,13 +56,13 @@ declare const datasetResultSchema: z.ZodObject<{
61
56
  offset: number;
62
57
  hasMore: boolean;
63
58
  } | undefined;
64
- }, {
65
- tenant?: string | undefined;
66
59
  cache?: {
67
60
  hit: boolean;
68
61
  ageMs?: number | undefined;
69
62
  stale?: boolean | undefined;
70
63
  } | undefined;
64
+ }, {
65
+ tenant?: string | undefined;
71
66
  timingMs?: number | undefined;
72
67
  sql?: string | undefined;
73
68
  rowCount?: number | undefined;
@@ -76,16 +71,16 @@ declare const datasetResultSchema: z.ZodObject<{
76
71
  offset: number;
77
72
  hasMore: boolean;
78
73
  } | undefined;
79
- }>>;
80
- }, "strip", z.ZodTypeAny, {
81
- data: Record<string, unknown>[];
82
- meta?: {
83
- tenant?: string | undefined;
84
74
  cache?: {
85
75
  hit: boolean;
86
76
  ageMs?: number | undefined;
87
77
  stale?: boolean | undefined;
88
78
  } | undefined;
79
+ }>>;
80
+ }, "strip", z.ZodTypeAny, {
81
+ data: Record<string, unknown>[];
82
+ meta?: {
83
+ tenant?: string | undefined;
89
84
  timingMs?: number | undefined;
90
85
  sql?: string | undefined;
91
86
  rowCount?: number | undefined;
@@ -94,16 +89,16 @@ declare const datasetResultSchema: z.ZodObject<{
94
89
  offset: number;
95
90
  hasMore: boolean;
96
91
  } | undefined;
97
- } | undefined;
98
- }, {
99
- data: Record<string, unknown>[];
100
- meta?: {
101
- tenant?: string | undefined;
102
92
  cache?: {
103
93
  hit: boolean;
104
94
  ageMs?: number | undefined;
105
95
  stale?: boolean | undefined;
106
96
  } | undefined;
97
+ } | undefined;
98
+ }, {
99
+ data: Record<string, unknown>[];
100
+ meta?: {
101
+ tenant?: string | undefined;
107
102
  timingMs?: number | undefined;
108
103
  sql?: string | undefined;
109
104
  rowCount?: number | undefined;
@@ -112,6 +107,11 @@ declare const datasetResultSchema: z.ZodObject<{
112
107
  offset: number;
113
108
  hasMore: boolean;
114
109
  } | undefined;
110
+ cache?: {
111
+ hit: boolean;
112
+ ageMs?: number | undefined;
113
+ stale?: boolean | undefined;
114
+ } | undefined;
115
115
  } | undefined;
116
116
  }>;
117
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>;
@@ -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,aAAa,EACb,uBAAuB,EACxB,MAAM,qBAAqB,CAAC;AAQ7B,OAAO,EAAuB,KAAK,YAAY,EAAE,MAAM,0BAA0B,CAAC;AAIlF,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"}
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;AAS7B,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,CAmH1E"}
@@ -9,11 +9,11 @@
9
9
  * - Returns { data } or { data, meta } based on headers
10
10
  */
11
11
  import { z } from 'zod';
12
+ import { buildDatasetInputSchema } from '@hypequery/datasets';
12
13
  import { ServeHttpError } from '../../errors.js';
13
14
  import { resolveSemanticExecutionRuntime, resolveSemanticQueryBuilder, } from '../query-builder-context.js';
14
15
  import { buildDatasetQueryDescription } from './utils/dataset-query-metadata.js';
15
16
  import { resolveDatasetEntry } from './utils/dataset-entry.js';
16
- import { buildDatasetInputSchema } from './utils/semantic-input-schema.js';
17
17
  import { resolveLocalAuthRequirement } from '../../auth-requirement.js';
18
18
  // ---------------------------------------------------------------------------
19
19
  // Zod schemas for dataset query input / output
@@ -47,7 +47,16 @@ export function createDatasetEndpoint(name, entry, analytics, builderFactory) {
47
47
  const effectiveMaxLimit = resolved.maxLimit ?? ds.limits?.maxResultSize ?? 1000;
48
48
  // Build a schema whose dimension/measure/filter fields are enumerated from
49
49
  // this dataset's contract, so OpenAPI/docs and clients see the valid fields.
50
- const datasetQueryInputSchema = buildDatasetInputSchema(ds);
50
+ const datasetQueryInputSchema = buildDatasetInputSchema(ds, {
51
+ includeMeta: true,
52
+ requireSelection: false,
53
+ enforceResultLimit: false,
54
+ maxOffset: undefined,
55
+ maxDimensions: undefined,
56
+ maxMeasures: undefined,
57
+ maxFilters: undefined,
58
+ maxOrderBy: undefined,
59
+ });
51
60
  const metadata = {
52
61
  path: '', // filled by router.register
53
62
  method: 'POST',
@@ -1,5 +1,5 @@
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';
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, publishDatasets, } 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, DatasetPublisher, PublishableMetric, PublishedMetricHandle, PublishedMetricMap, PublishedMetrics, PublishedDataset, PublishedDatasetRegistry, PublishDatasetOptions, } from '@hypequery/datasets';
3
3
  export { createMetricEndpoint } from './metric-endpoint.js';
4
4
  export { createDatasetEndpoint } from './dataset-endpoint.js';
5
5
  export { createSemanticContractEndpoint, buildSemanticContractSource, } from './contract-endpoint.js';
@@ -1 +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,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
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,EACrB,eAAe,GAChB,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,EACjB,gBAAgB,EAChB,iBAAiB,EACjB,qBAAqB,EACrB,kBAAkB,EAClB,gBAAgB,EAChB,gBAAgB,EAChB,wBAAwB,EACxB,qBAAqB,GACtB,MAAM,qBAAqB,CAAC;AAG7B,OAAO,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC"}
@@ -1,5 +1,5 @@
1
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';
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, publishDatasets, } from '@hypequery/datasets';
3
3
  // Serve-specific endpoint integration
4
4
  export { createMetricEndpoint } from './metric-endpoint.js';
5
5
  export { createDatasetEndpoint } from './dataset-endpoint.js';
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { z } from 'zod';
10
10
  import type { AuthContext, AuthStrategy, MetricEntry, ServeEndpoint, ServeMiddleware, TenantConfigOverride } from '../../types.js';
11
- import type { DatasetClient, MetricHandle, QueryBuilderFactoryLike } from '@hypequery/datasets';
11
+ import { type DatasetClient, type MetricHandle, type QueryBuilderFactoryLike } from '@hypequery/datasets';
12
12
  declare const metricResultSchema: z.ZodObject<{
13
13
  data: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>, "many">;
14
14
  meta: z.ZodOptional<z.ZodObject<{
@@ -44,11 +44,6 @@ declare const metricResultSchema: z.ZodObject<{
44
44
  }>>;
45
45
  }, "strip", z.ZodTypeAny, {
46
46
  tenant?: string | undefined;
47
- cache?: {
48
- hit: boolean;
49
- ageMs?: number | undefined;
50
- stale?: boolean | undefined;
51
- } | undefined;
52
47
  timingMs?: number | undefined;
53
48
  sql?: string | undefined;
54
49
  rowCount?: number | undefined;
@@ -57,13 +52,13 @@ declare const metricResultSchema: z.ZodObject<{
57
52
  offset: number;
58
53
  hasMore: boolean;
59
54
  } | undefined;
60
- }, {
61
- tenant?: string | undefined;
62
55
  cache?: {
63
56
  hit: boolean;
64
57
  ageMs?: number | undefined;
65
58
  stale?: boolean | undefined;
66
59
  } | undefined;
60
+ }, {
61
+ tenant?: string | undefined;
67
62
  timingMs?: number | undefined;
68
63
  sql?: string | undefined;
69
64
  rowCount?: number | undefined;
@@ -72,16 +67,16 @@ declare const metricResultSchema: z.ZodObject<{
72
67
  offset: number;
73
68
  hasMore: boolean;
74
69
  } | undefined;
75
- }>>;
76
- }, "strip", z.ZodTypeAny, {
77
- data: Record<string, unknown>[];
78
- meta?: {
79
- tenant?: string | undefined;
80
70
  cache?: {
81
71
  hit: boolean;
82
72
  ageMs?: number | undefined;
83
73
  stale?: boolean | undefined;
84
74
  } | undefined;
75
+ }>>;
76
+ }, "strip", z.ZodTypeAny, {
77
+ data: Record<string, unknown>[];
78
+ meta?: {
79
+ tenant?: string | undefined;
85
80
  timingMs?: number | undefined;
86
81
  sql?: string | undefined;
87
82
  rowCount?: number | undefined;
@@ -90,16 +85,16 @@ declare const metricResultSchema: z.ZodObject<{
90
85
  offset: number;
91
86
  hasMore: boolean;
92
87
  } | undefined;
93
- } | undefined;
94
- }, {
95
- data: Record<string, unknown>[];
96
- meta?: {
97
- tenant?: string | undefined;
98
88
  cache?: {
99
89
  hit: boolean;
100
90
  ageMs?: number | undefined;
101
91
  stale?: boolean | undefined;
102
92
  } | undefined;
93
+ } | undefined;
94
+ }, {
95
+ data: Record<string, unknown>[];
96
+ meta?: {
97
+ tenant?: string | undefined;
103
98
  timingMs?: number | undefined;
104
99
  sql?: string | undefined;
105
100
  rowCount?: number | undefined;
@@ -108,6 +103,11 @@ declare const metricResultSchema: z.ZodObject<{
108
103
  offset: number;
109
104
  hasMore: boolean;
110
105
  } | undefined;
106
+ cache?: {
107
+ hit: boolean;
108
+ ageMs?: number | undefined;
109
+ stale?: boolean | undefined;
110
+ } | undefined;
111
111
  } | undefined;
112
112
  }>;
113
113
  export declare function resolveMetricEntry<TAuth extends AuthContext>(entry: MetricEntry<TAuth>): {
@@ -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;AA8BpI,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,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,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"}
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,EAGL,KAAK,aAAa,EAElB,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC7B,MAAM,qBAAqB,CAAC;AA6B7B,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,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,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,CAuHzE"}
@@ -7,9 +7,9 @@
7
7
  * - Returns { data } or { data, meta } based on headers
8
8
  */
9
9
  import { z } from 'zod';
10
+ import { buildMetricInputSchema, } from '@hypequery/datasets';
10
11
  import { ServeHttpError } from '../../errors.js';
11
12
  import { resolveSemanticExecutionRuntime, resolveSemanticQueryBuilder, } from '../query-builder-context.js';
12
- import { buildMetricInputSchema } from './utils/semantic-input-schema.js';
13
13
  import { resolveLocalAuthRequirement } from '../../auth-requirement.js';
14
14
  // ---------------------------------------------------------------------------
15
15
  // Zod schemas for metric query input / output
@@ -62,10 +62,18 @@ export function createMetricEndpoint(name, entry, analytics, defaultBuilderFacto
62
62
  // The metric's underlying dataset, used to enumerate valid query fields and
63
63
  // page-size defaults.
64
64
  const ds = (metricRef.__type === 'metric_ref' ? metricRef.dataset : metricRef.metric.dataset);
65
- const metricQueryInputSchema = buildMetricInputSchema(ds, contract.name);
66
65
  // Page-size cap, mirroring datasets: clamp (don't reject) and apply a default
67
66
  // so a metric query is never unbounded.
68
67
  const effectiveMaxLimit = resolved.maxLimit ?? ds.limits?.maxResultSize ?? 1000;
68
+ const metricQueryInputSchema = buildMetricInputSchema(ds, contract.name, {
69
+ includeMeta: true,
70
+ enforceResultLimit: false,
71
+ maxOffset: undefined,
72
+ maxDimensions: undefined,
73
+ maxMeasures: undefined,
74
+ maxFilters: undefined,
75
+ maxOrderBy: undefined,
76
+ }, contract);
69
77
  const metadata = {
70
78
  path: '', // filled by router.register
71
79
  method: 'POST',
@@ -1,107 +1,6 @@
1
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.
2
+ * @deprecated Import these canonical schema builders from
3
+ * `@hypequery/datasets`. This compatibility module contains no schema logic.
12
4
  */
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.ZodTypeAny>;
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?: any;
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?: any;
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.ZodTypeAny>;
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?: any;
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?: any;
105
- includeMeta?: boolean | undefined;
106
- }>;
5
+ export { buildDatasetInputSchema, buildMetricInputSchema, } from '@hypequery/datasets';
107
6
  //# sourceMappingURL=semantic-input-schema.d.ts.map
@@ -1 +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,EAIL,KAAK,kBAAkB,EAExB,MAAM,qBAAqB,CAAC;AAmC7B;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,EAAE,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwB7D;AAED;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,EAAE,kBAAkB,EAAE,UAAU,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BhF"}
1
+ {"version":3,"file":"semantic-input-schema.d.ts","sourceRoot":"","sources":["../../../../src/semantic/datasets/utils/semantic-input-schema.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,EACL,uBAAuB,EACvB,sBAAsB,GACvB,MAAM,qBAAqB,CAAC"}
@@ -1,104 +1,5 @@
1
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.
2
+ * @deprecated Import these canonical schema builders from
3
+ * `@hypequery/datasets`. This compatibility module contains no schema logic.
12
4
  */
13
- import { z } from 'zod';
14
- import { getDatasetCatalog, getQueryableRelationshipFields, SEMANTIC_FILTER_OPERATORS, } from '@hypequery/datasets';
15
- /** An enum over the given field names, or a plain string when none are known. */
16
- function fieldEnum(values) {
17
- const unique = Array.from(new Set(values));
18
- return unique.length > 0
19
- ? z.enum(unique)
20
- : z.string();
21
- }
22
- function grainEnum(catalog) {
23
- return fieldEnum(catalog.supportedGrains);
24
- }
25
- function filterSchema(fieldNames) {
26
- return z.object({
27
- field: fieldEnum(fieldNames),
28
- operator: z.enum(SEMANTIC_FILTER_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 catalog = getDatasetCatalog(ds);
49
- const dimensionNames = [
50
- ...Object.keys(catalog.dimensions),
51
- ...getQueryableRelationshipFields(catalog),
52
- ];
53
- const measureNames = Object.keys(catalog.measures);
54
- // Dataset runtime (`validateDatasetQueryInput`) rejects any non-relationship
55
- // field not in `catalog.filters`, so mirror that exactly (no dimension fallback).
56
- const filterFieldNames = [
57
- ...Object.keys(catalog.filters),
58
- ...getQueryableRelationshipFields(catalog),
59
- ];
60
- return z.object({
61
- dimensions: boundedArray(fieldEnum(dimensionNames), ds.limits?.maxDimensions),
62
- measures: boundedArray(fieldEnum(measureNames), ds.limits?.maxMeasures),
63
- filters: boundedArray(filterSchema(filterFieldNames), ds.limits?.maxFilters),
64
- orderBy: z.array(orderBySchema(catalog.orderableFields)).optional(),
65
- limit: z.number().int().positive().optional(),
66
- offset: z.number().int().nonnegative().optional(),
67
- by: grainEnum(catalog).optional(),
68
- includeMeta: z.boolean().optional(),
69
- }).strict();
70
- }
71
- /**
72
- * Input schema for a metric query endpoint, mirroring the metric `validateQuery`.
73
- * Metrics select a single value, so there is no `measures` field; `orderBy` may
74
- * reference the metric's own output column (`metricName`).
75
- */
76
- export function buildMetricInputSchema(ds, metricName) {
77
- const catalog = getDatasetCatalog(ds);
78
- const dimensionNames = [
79
- ...Object.keys(catalog.dimensions),
80
- ...getQueryableRelationshipFields(catalog),
81
- ];
82
- const orderableNames = [
83
- ...dimensionNames,
84
- metricName,
85
- ...(catalog.timeKey ? ['period'] : []),
86
- ];
87
- // Metric runtime (`validateQuery`) falls back to all dimensions as valid
88
- // filter fields when the dataset declares no filters — mirror that fallback so
89
- // the schema never rejects a local-dimension filter the runtime would accept.
90
- const declaredFilters = Object.keys(catalog.filters);
91
- const filterFieldNames = [
92
- ...(declaredFilters.length > 0 ? declaredFilters : Object.keys(catalog.dimensions)),
93
- ...getQueryableRelationshipFields(catalog),
94
- ];
95
- return z.object({
96
- dimensions: boundedArray(fieldEnum(dimensionNames), ds.limits?.maxDimensions),
97
- filters: boundedArray(filterSchema(filterFieldNames), ds.limits?.maxFilters),
98
- orderBy: z.array(orderBySchema(orderableNames)).optional(),
99
- limit: z.number().int().positive().optional(),
100
- offset: z.number().int().nonnegative().optional(),
101
- by: grainEnum(catalog).optional(),
102
- includeMeta: z.boolean().optional(),
103
- });
104
- }
5
+ export { buildDatasetInputSchema, buildMetricInputSchema, } from '@hypequery/datasets';
@@ -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;AAkCrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAqRN,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;AAmCrB;;;;;;;;;;;;;;;;;;;;;;;;;;;;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,CAuSN,CAAC"}
@@ -10,6 +10,7 @@ import { createAPImethods } from "./api-builder.js";
10
10
  import { createCacheObservability, detectBuilderCache } from "../cache-observability.js";
11
11
  import { createDatasetClient, serializeSemanticContract, toQueryBuilderFactory } from "@hypequery/datasets";
12
12
  import { createMetricEndpoint, createDatasetEndpoint, createSemanticContractEndpoint, buildSemanticContractSource, } from "../semantic/datasets/index.js";
13
+ import { attachServeMcpSource } from "./mcp-source.js";
13
14
  import { attachSemanticQueryBuilder, extractQueryBuilderFromContext } from "../semantic/query-builder-context.js";
14
15
  import { buildProtocolDeploymentContract } from "../protocol-adapter.js";
15
16
  const assertSemanticKeyAvailable = (queryEntries, key, kind) => {
@@ -231,6 +232,21 @@ export const createAPI = (config) => {
231
232
  getBuilderCache: () => detectBuilderCache(resolvedQueryBuilder),
232
233
  });
233
234
  const api = createAPImethods(queryEntries, queryLogger, router, authStrategies, globalMiddlewares, executeQuery, handler, basePath, cacheObservability, options => buildProtocolDeploymentContract(config, options), Object.keys(configuredQueries));
235
+ // Expose the registered datasets to `hypequery mcp`, so the CLI serves the
236
+ // same semantic model as this entrypoint rather than a second MCP config.
237
+ if (config.datasets) {
238
+ const mcpDatasets = buildSemanticContractSource(config.datasets, config.metrics);
239
+ attachServeMcpSource(api, {
240
+ datasets: mcpDatasets,
241
+ resolveAnalytics: () => {
242
+ if (!resolvedQueryBuilder) {
243
+ throw new Error('A query builder is required to execute datasets over MCP. Pass `queryBuilder` '
244
+ + 'to defineServe, or expose it as `context.db`.');
245
+ }
246
+ return getAnalytics(resolvedQueryBuilder);
247
+ },
248
+ });
249
+ }
234
250
  if (openapiConfig.enabled) {
235
251
  const openapiEndpoint = createOpenApiEndpoint(openapiConfig.path, () => router.list(), config.openapi);
236
252
  router.register(openapiEndpoint);
@@ -0,0 +1,16 @@
1
+ import type { DatasetCatalogSource, DatasetClient } from '@hypequery/datasets';
2
+ export interface ServeMcpSource {
3
+ readonly version: 1;
4
+ /** Datasets keyed as registered, with named metrics grouped onto each. */
5
+ readonly datasets: Readonly<Record<string, DatasetCatalogSource>>;
6
+ /**
7
+ * Resolves the shared semantic client. Deferred because the client is only
8
+ * constructed once a semantic endpoint needs it, and an application with no
9
+ * configured query builder should fail when MCP starts rather than at import.
10
+ */
11
+ readonly resolveAnalytics: () => DatasetClient;
12
+ }
13
+ export declare function attachServeMcpSource(target: object, source: Omit<ServeMcpSource, 'version'>): void;
14
+ /** Read the MCP source off a loaded Serve entrypoint, if it registered datasets. */
15
+ export declare function readServeMcpSource(value: unknown): ServeMcpSource | undefined;
16
+ //# sourceMappingURL=mcp-source.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-source.d.ts","sourceRoot":"","sources":["../../src/server/mcp-source.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAa/E,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,0EAA0E;IAC1E,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC,CAAC;IAClE;;;;OAIG;IACH,QAAQ,CAAC,gBAAgB,EAAE,MAAM,aAAa,CAAC;CAChD;AAED,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,SAAS,CAAC,GACtC,IAAI,CAWN;AAED,oFAAoF;AACpF,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,cAAc,GAAG,SAAS,CAM7E"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Lets `hypequery mcp` serve an application's datasets from the same
3
+ * entrypoint local development already uses, instead of asking the author to
4
+ * maintain a second MCP config that can drift from it.
5
+ *
6
+ * Attached under a registered symbol rather than a public property: it is a
7
+ * build/tooling seam, not part of the API surface an application calls. The
8
+ * same approach as `attachDeploymentBuildSource`.
9
+ */
10
+ const mcpSourceSymbol = Symbol.for('hypequery.mcp-source.v1');
11
+ export function attachServeMcpSource(target, source) {
12
+ Object.defineProperty(target, mcpSourceSymbol, {
13
+ value: Object.freeze({
14
+ version: 1,
15
+ datasets: Object.freeze({ ...source.datasets }),
16
+ resolveAnalytics: source.resolveAnalytics,
17
+ }),
18
+ enumerable: false,
19
+ configurable: false,
20
+ writable: false,
21
+ });
22
+ }
23
+ /** Read the MCP source off a loaded Serve entrypoint, if it registered datasets. */
24
+ export function readServeMcpSource(value) {
25
+ if (value === null || (typeof value !== 'object' && typeof value !== 'function')) {
26
+ return undefined;
27
+ }
28
+ const source = value[mcpSourceSymbol];
29
+ return isMcpSource(source) ? source : undefined;
30
+ }
31
+ function isMcpSource(value) {
32
+ return (typeof value === 'object'
33
+ && value !== null
34
+ && value.version === 1
35
+ && typeof value.resolveAnalytics === 'function');
36
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/serve",
3
- "version": "0.16.1",
3
+ "version": "0.17.0",
4
4
  "description": "Code-first TypeScript runtime for validated ClickHouse analytics APIs, OpenAPI, auth, and tenancy",
5
5
  "keywords": [
6
6
  "clickhouse",
@@ -34,12 +34,12 @@
34
34
  "dist"
35
35
  ],
36
36
  "dependencies": {
37
- "@hypequery/protocol": "^0.11.0",
37
+ "@hypequery/protocol": "^0.12.0",
38
38
  "jose": "^5.9.6",
39
39
  "openapi-typescript": "^7.13.0",
40
40
  "zod": "^3.23.8",
41
41
  "zod-to-json-schema": "^3.23.5",
42
- "@hypequery/datasets": "^0.13.5"
42
+ "@hypequery/datasets": "^0.14.0"
43
43
  },
44
44
  "peerDependencies": {
45
45
  "tsx": "^4.0.0",