@hypequery/serve 0.15.1 → 0.15.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +28 -216
  2. package/package.json +18 -5
package/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @hypequery/serve
2
2
 
3
- Code-first runtime for turning hypequery queries into reusable contracts, direct execution helpers, and HTTP routes.
3
+ Turn TypeScript ClickHouse queries into trusted analytics APIs.
4
4
 
5
- Use it when a local query should become something the rest of your app can call consistently.
5
+ `@hypequery/serve` lets one query contract run inside your app, over HTTP, through OpenAPI, in typed React hooks, or as an agent tool. Inputs are validated, outputs stay typed, and authentication and tenant context live at the server boundary.
6
6
 
7
7
  ## Install
8
8
 
@@ -10,9 +10,7 @@ Use it when a local query should become something the rest of your app can call
10
10
  npm install @hypequery/serve zod
11
11
  ```
12
12
 
13
- `tsx` is an optional peer dependency used by the local dev workflow.
14
-
15
- ## Quick Start
13
+ ## Define once
16
14
 
17
15
  ```ts
18
16
  import { initServe } from '@hypequery/serve';
@@ -25,245 +23,59 @@ const { query, serve } = initServe({
25
23
  });
26
24
 
27
25
  const weeklyRevenue = query({
28
- description: 'Calculate weekly revenue',
26
+ description: 'Weekly revenue since a given date',
29
27
  input: z.object({ startDate: z.string() }),
30
28
  query: ({ ctx, input }) =>
31
29
  ctx.db
32
30
  .table('orders')
33
31
  .where('created_at', 'gte', input.startDate)
34
- .sum('total', 'revenue')
32
+ .sum('amount', 'revenue')
35
33
  .execute(),
36
34
  });
37
35
 
38
- export const api = serve({
39
- queries: { weeklyRevenue },
40
- });
41
-
42
- api.route('/weeklyRevenue', api.queries.weeklyRevenue);
36
+ export const api = serve({ queries: { weeklyRevenue } });
37
+ api.route('/weekly-revenue', api.queries.weeklyRevenue);
43
38
  ```
44
39
 
45
- Now you can:
46
-
47
- - call `api.execute('weeklyRevenue', { input: ... })` in process
48
- - expose the same query over HTTP
49
- - consume it from `@hypequery/react`
50
- - describe it for tools and agents
51
-
52
- The same `api.execute(...)` call works for configured metrics and datasets:
40
+ ## Run anywhere
53
41
 
54
42
  ```ts
55
- import { initServe } from '@hypequery/serve';
56
- import { createQueryBuilder } from '@hypequery/clickhouse';
57
- import { dataset, dimension, measure } from '@hypequery/datasets';
58
-
59
- const Orders = dataset('orders', {
60
- source: 'orders',
61
- dimensions: {
62
- country: dimension.string(),
63
- },
64
- measures: {
65
- revenue: measure.sum('amount'),
66
- },
67
- });
68
-
69
- const revenue = Orders.metric('revenue', { measure: 'revenue' });
70
- const queryBuilder = createQueryBuilder({ url, username, password, database });
71
-
72
- const { serve } = initServe({
73
- context: () => ({ db: queryBuilder }), // ✅ Pass queryBuilder via context once
74
- });
75
-
76
- const api = serve({
77
- metrics: { revenue }, // ✅ Auto-extracts queryBuilder from context
78
- datasets: { orders: Orders },
79
- });
80
-
81
- await api.execute('revenue', {
82
- input: { dimensions: ['country'] },
83
- });
84
-
85
- await api.execute('dataset:orders', {
86
- input: { dimensions: ['country'], measures: ['revenue'] },
43
+ await api.execute('weeklyRevenue', {
44
+ input: { startDate: '2026-01-01' },
87
45
  });
88
46
  ```
89
47
 
90
- ### Semantic endpoint auth
48
+ The same contract can power a REST endpoint, generated OpenAPI, a typed `@hypequery/react` hook, or a tool description for an AI agent.
91
49
 
92
- Dataset and metric entries follow the same auth rules as named queries. An
93
- entry with no local strategy, including `auth: null`, still inherits the global
94
- `auth` configuration. Use `requiresAuth: false` for an explicitly public
95
- endpoint; `requiredRoles` or `requiredScopes` always require authentication.
50
+ Serve also accepts semantic metrics and datasets directly:
96
51
 
97
52
  ```ts
98
- const api = serve({
99
- auth: globalAuth,
100
- metrics: {
101
- revenue: { metric: revenue, auth: null }, // inherits globalAuth
102
- },
103
- datasets: {
104
- publicOrders: { dataset: Orders, requiresAuth: false },
105
- },
106
- });
107
- ```
108
-
109
- ## Main Ideas
110
-
111
- ### `query({ ... })`
112
-
113
- Defines a typed contract:
114
-
115
- - description
116
- - optional input schema
117
- - query implementation
118
-
119
- Standalone queries can execute without creating a served API:
120
-
121
- ```ts
122
- const topCustomers = query({
123
- input: z.object({ limit: z.number().int().positive() }),
124
- query: async ({ input }) =>
125
- db
126
- .table('orders')
127
- .select(['customer_id'])
128
- .sum('total', 'revenue')
129
- .groupBy('customer_id')
130
- .limit(input.limit)
131
- .execute(),
132
- });
133
-
134
- await topCustomers.execute({
135
- input: { limit: 10 },
136
- });
137
- ```
138
-
139
- ### `serve({ queries, metrics, datasets })`
140
-
141
- Builds a runtime around those contracts:
142
-
143
- - direct execution
144
- - route registration
145
- - docs and OpenAPI support
146
- - hooks, auth, and tenancy features when needed
147
-
148
- ## Common Example
149
-
150
- ```ts
151
- const topCustomers = query({
152
- description: 'Top customers by revenue',
153
- input: z.object({ limit: z.number().int().positive().default(10) }),
154
- query: ({ ctx, input }) =>
155
- ctx.db
156
- .table('orders')
157
- .select(['customer_id'])
158
- .sum('total', 'revenue')
159
- .groupBy('customer_id')
160
- .orderBy('revenue', 'DESC')
161
- .limit(input.limit)
162
- .execute(),
163
- });
164
-
165
53
  export const api = serve({
166
- queries: { topCustomers },
167
- });
168
-
169
- api.route('/topCustomers', api.queries.topCustomers);
170
- ```
171
-
172
- ## Authentication
173
-
174
- Pass an auth strategy (or array of strategies) to `serve({ auth })` / `createAPI({ auth })`.
175
- When auth is configured, endpoints require authentication by default. Mark exceptions
176
- with `query.public()`.
177
-
178
- For same-app APIs, prefer reading the host app's authenticated request context:
179
-
180
- ```ts
181
- import { createAPI, fromContext } from '@hypequery/serve';
182
-
183
- const api = createAPI({
184
- queryBuilder: db,
185
- datasets,
186
- auth: fromContext(({ request }) => {
187
- const user = getUserFromRequest(request.raw);
188
- return user
189
- ? { userId: user.id, tenantId: user.orgId, roles: user.roles }
190
- : null;
191
- }),
192
- tenant: {
193
- extract: (auth) => auth.tenantId,
194
- column: 'tenant_id',
195
- mode: 'auto-inject',
196
- },
197
- });
198
- ```
199
-
200
- For cross-origin embedding, verify JWT bearer tokens with a shared secret or a
201
- provider JWKS:
202
-
203
- ```ts
204
- import { createJwtStrategy } from '@hypequery/serve';
205
-
206
- const auth = createJwtStrategy({
207
- // Use `secret` for HS256 tokens you mint yourself.
208
- secret: process.env.HYPEQUERY_AUTH_SECRET!,
209
- issuer: 'https://your-app.example.com',
210
- audience: 'hypequery-analytics',
211
- });
212
-
213
- const providerAuth = createJwtStrategy({
214
- // Use `jwksUri` for Auth0/Clerk/Cognito/etc.
215
- jwksUri: 'https://example.auth0.com/.well-known/jwks.json',
216
- issuer: 'https://example.auth0.com/',
217
- audience: 'https://api.example.com',
218
- });
219
- ```
220
-
221
- For signed embedding, mint short-lived analytics tokens server-side:
222
-
223
- ```ts
224
- import { createAnalyticsTokenIssuer } from '@hypequery/serve';
225
-
226
- const issueAnalyticsToken = createAnalyticsTokenIssuer({
227
- secret: process.env.HYPEQUERY_AUTH_SECRET!,
228
- expiresIn: '15m',
229
- issuer: 'https://your-app.example.com',
230
- audience: 'hypequery-analytics',
231
- });
232
-
233
- app.get('/api/analytics/token', requireUser, async (req, res) => {
234
- res.json({
235
- token: await issueAnalyticsToken({
236
- userId: req.user.id,
237
- tenantId: req.user.orgId,
238
- roles: req.user.roles,
239
- }),
240
- });
54
+ metrics: { revenue },
55
+ datasets: { orders: Orders },
241
56
  });
242
57
  ```
243
58
 
244
- `createApiKeyStrategy` and `createBearerTokenStrategy` remain available for custom
245
- authentication systems.
246
-
247
- > **Rate limiting:** the default `RateLimitStore` is in-memory and therefore
248
- > per-instance. Behind multiple instances, supply a shared store (e.g. Redis) by
249
- > implementing the `RateLimitStore` interface.
250
-
251
- ## Adapters And Runtimes
59
+ ## Built for real analytics APIs
252
60
 
253
- `@hypequery/serve` can be used behind different runtimes and adapters, but most users should start with the standard `initServe(...).serve(...)` path and the CLI dev server.
61
+ - zod input and output validation;
62
+ - authentication, roles, scopes, and explicit public routes;
63
+ - trusted multi-tenant context and automatic tenant predicates;
64
+ - OpenAPI and static React route manifests;
65
+ - CORS, rate limiting, request IDs, logging, and cache observability;
66
+ - Node and Fetch adapters for existing application stacks.
254
67
 
255
- If you need framework-specific integration, see the docs for:
68
+ ## Why it matters
256
69
 
257
- - Node handlers
258
- - Fetch handlers
259
- - OpenAPI generation
260
- - auth and middleware
70
+ Analytics endpoints usually drift across three copies: the SQL, the API type, and the frontend client. Serve keeps them on one TypeScript contract while leaving deployment and framework choices with your application.
261
71
 
262
- ## Docs
72
+ ## Learn more
263
73
 
264
74
  - [Core concepts](https://hypequery.com/docs/core-concepts)
265
- - [Serve runtime reference](https://hypequery.com/docs/reference/runtime)
266
- - [CLI reference](https://hypequery.com/docs/reference/api/cli)
75
+ - [HTTP and OpenAPI](https://hypequery.com/docs/http-openapi)
76
+ - [Authentication](https://hypequery.com/docs/authentication)
77
+ - [Multi-tenancy](https://hypequery.com/docs/multi-tenancy)
78
+ - [Current capabilities](https://hypequery.com/docs/capabilities)
267
79
 
268
80
  ## License
269
81
 
package/package.json CHANGED
@@ -1,7 +1,19 @@
1
1
  {
2
2
  "name": "@hypequery/serve",
3
- "version": "0.15.1",
4
- "description": "Declarative HTTP server for exposing hypequery analytics endpoints",
3
+ "version": "0.15.3",
4
+ "description": "Code-first TypeScript runtime for validated ClickHouse analytics APIs, OpenAPI, auth, and tenancy",
5
+ "keywords": [
6
+ "clickhouse",
7
+ "typescript",
8
+ "analytics-api",
9
+ "semantic-layer",
10
+ "query-builder",
11
+ "orm",
12
+ "openapi",
13
+ "multi-tenant",
14
+ "http",
15
+ "zod"
16
+ ],
5
17
  "license": "Apache-2.0",
6
18
  "type": "module",
7
19
  "main": "dist/index.js",
@@ -22,12 +34,12 @@
22
34
  "dist"
23
35
  ],
24
36
  "dependencies": {
25
- "@hypequery/protocol": "^0.10.0",
37
+ "@hypequery/protocol": "^0.11.0",
26
38
  "jose": "^5.9.6",
27
39
  "openapi-typescript": "^7.13.0",
28
40
  "zod": "^3.23.8",
29
41
  "zod-to-json-schema": "^3.23.5",
30
- "@hypequery/datasets": "^0.13.2"
42
+ "@hypequery/datasets": "^0.13.4"
31
43
  },
32
44
  "peerDependencies": {
33
45
  "tsx": "^4.0.0"
@@ -46,7 +58,8 @@
46
58
  },
47
59
  "repository": {
48
60
  "type": "git",
49
- "url": "https://github.com/hypequery/hypequery.git"
61
+ "url": "git+https://github.com/hypequery/hypequery.git",
62
+ "directory": "packages/serve"
50
63
  },
51
64
  "homepage": "https://hypequery.com",
52
65
  "bugs": {