@hypequery/serve 0.16.2 → 0.17.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cloud-compatibility.d.ts +44 -0
- package/dist/cloud-compatibility.d.ts.map +1 -0
- package/dist/cloud-compatibility.js +188 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/protocol-adapter.d.ts +11 -0
- package/dist/protocol-adapter.d.ts.map +1 -1
- package/dist/protocol-adapter.js +17 -0
- package/dist/semantic/datasets/dataset-endpoint.d.ts +17 -17
- package/dist/semantic/datasets/dataset-endpoint.d.ts.map +1 -1
- package/dist/semantic/datasets/dataset-endpoint.js +11 -2
- package/dist/semantic/datasets/index.d.ts +2 -2
- package/dist/semantic/datasets/index.d.ts.map +1 -1
- package/dist/semantic/datasets/index.js +1 -1
- package/dist/semantic/datasets/metric-endpoint.d.ts +18 -18
- package/dist/semantic/datasets/metric-endpoint.d.ts.map +1 -1
- package/dist/semantic/datasets/metric-endpoint.js +10 -2
- package/dist/semantic/datasets/utils/semantic-input-schema.d.ts +3 -104
- package/dist/semantic/datasets/utils/semantic-input-schema.d.ts.map +1 -1
- package/dist/semantic/datasets/utils/semantic-input-schema.js +3 -102
- package/dist/server/create-api.d.ts.map +1 -1
- package/dist/server/create-api.js +16 -0
- package/dist/server/mcp-source.d.ts +16 -0
- package/dist/server/mcp-source.d.ts.map +1 -0
- package/dist/server/mcp-source.js +36 -0
- package/package.json +3 -3
|
@@ -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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/protocol-adapter.js
CHANGED
|
@@ -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;
|
|
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,
|
|
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
|
|
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,
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
|
|
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
|
|
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
|
-
*
|
|
3
|
-
*
|
|
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
|
-
|
|
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;
|
|
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.
|
|
3
|
+
"version": "0.17.1",
|
|
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.
|
|
37
|
+
"@hypequery/protocol": "^0.13.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.
|
|
42
|
+
"@hypequery/datasets": "^0.15.0"
|
|
43
43
|
},
|
|
44
44
|
"peerDependencies": {
|
|
45
45
|
"tsx": "^4.0.0",
|