@hypequery/deployment 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -3
- package/dist/control-plane.d.ts +4 -1
- package/dist/control-plane.d.ts.map +1 -1
- package/dist/control-plane.js +19 -0
- package/dist/data-plane-adapters.d.ts +18 -0
- package/dist/data-plane-adapters.d.ts.map +1 -0
- package/dist/data-plane-adapters.js +304 -0
- package/dist/data-plane.d.ts +6 -0
- package/dist/data-plane.d.ts.map +1 -1
- package/dist/data-plane.js +119 -110
- package/dist/filesystem-host.d.ts +51 -0
- package/dist/filesystem-host.d.ts.map +1 -0
- package/dist/filesystem-host.js +73 -0
- package/dist/host.d.ts +40 -0
- package/dist/host.d.ts.map +1 -0
- package/dist/host.js +183 -0
- package/dist/index.d.ts +8 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/runtime-supervisor.d.ts +6 -1
- package/dist/runtime-supervisor.d.ts.map +1 -1
- package/dist/runtime-supervisor.js +7 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
# @hypequery/deployment
|
|
2
2
|
|
|
3
|
-
Provider-neutral verification, intake, activation, and HTTP
|
|
3
|
+
Provider-neutral verification, intake, activation, runtime, and HTTP hosting
|
|
4
4
|
building blocks for Hypequery deployment bundles.
|
|
5
5
|
|
|
6
6
|
The package accepts the authenticated multipart transport emitted by
|
|
7
7
|
`hypequery deploy`, reconstructs only manifest-declared files in temporary
|
|
8
8
|
storage, and revalidates the release, bundle manifest, file hashes, deployment
|
|
9
|
-
identity, and runtime references before handing the submission to a store.
|
|
10
|
-
does not activate or execute a release.
|
|
9
|
+
identity, and runtime references before handing the submission to a store.
|
|
11
10
|
|
|
12
11
|
## Intake adapters
|
|
13
12
|
|
|
@@ -292,4 +291,58 @@ Semantic-plan and compiled-SQL adapters own database execution. The core passes
|
|
|
292
291
|
only validated values, the fixed implementation artifact, and closed typed SQL
|
|
293
292
|
parameter bindings; it does not select credentials or interpolate SQL.
|
|
294
293
|
|
|
294
|
+
## Data-plane hosting
|
|
295
|
+
|
|
296
|
+
`createDeploymentHost` keeps route/schema execution and supervised runtime
|
|
297
|
+
dispatch pinned to the same activation revision. Reconciliation builds a new
|
|
298
|
+
data plane from the supervisor's immutable generation view and publishes it
|
|
299
|
+
only after confirming that the active generation did not change during
|
|
300
|
+
configuration.
|
|
301
|
+
|
|
302
|
+
`createDeploymentDataPlaneFetchHandler` and
|
|
303
|
+
`createDeploymentDataPlaneNodeHandler` expose a hosted data plane over HTTP.
|
|
304
|
+
They accept either query parameters or one bounded UTF-8 JSON body, reject
|
|
305
|
+
duplicate JSON property names, forward cancellation, and return bounded JSON
|
|
306
|
+
errors. Public cache metadata is emitted only when execution confirms the
|
|
307
|
+
request was public, tenant-independent, and unauthenticated.
|
|
308
|
+
|
|
309
|
+
For a single-node service, `createFileSystemDeploymentHost` composes the
|
|
310
|
+
filesystem submission store, activation registry, intake, control plane,
|
|
311
|
+
runtime materializer, supervisor, and generation-pinned data plane. It
|
|
312
|
+
reconciles configured targets at startup and schedules reconciliation after a
|
|
313
|
+
durable activation without changing an already-successful activation response
|
|
314
|
+
if runtime startup later fails.
|
|
315
|
+
|
|
316
|
+
```ts
|
|
317
|
+
import {
|
|
318
|
+
createDeploymentDataPlaneNodeHandler,
|
|
319
|
+
createFileSystemDeploymentHost,
|
|
320
|
+
} from '@hypequery/deployment';
|
|
321
|
+
|
|
322
|
+
const service = createFileSystemDeploymentHost({
|
|
323
|
+
directory: '/var/lib/hypequery/deployments',
|
|
324
|
+
targets: [{ project: 'analytics', environment: 'production' }],
|
|
325
|
+
intake: { authenticator: deploymentAuthenticator, authorizer: deploymentAuthorizer },
|
|
326
|
+
controlPlane: { authenticator: operatorAuthenticator, authorizer: operatorAuthorizer },
|
|
327
|
+
configureDataPlane: () => ({
|
|
328
|
+
authenticate: queryAuthenticator,
|
|
329
|
+
resolveTenant,
|
|
330
|
+
executeSemanticPlan,
|
|
331
|
+
executeCompiledSql,
|
|
332
|
+
runtimeArgument: ({ input, principal, tenant }) => ({ input, principal, tenant }),
|
|
333
|
+
}),
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
await service.start();
|
|
337
|
+
const queryHandler = createDeploymentDataPlaneNodeHandler(
|
|
338
|
+
service.host.dataPlane({ project: 'analytics', environment: 'production' }),
|
|
339
|
+
);
|
|
340
|
+
```
|
|
341
|
+
|
|
342
|
+
Cloud and other distributed systems can use the same host, supervisor, and HTTP
|
|
343
|
+
adapter interfaces while supplying provider-owned persistence, runtime, SQL,
|
|
344
|
+
authentication, tenant, routing, and observability implementations. The
|
|
345
|
+
filesystem assembly is a reference single-host composition, not a distributed
|
|
346
|
+
control plane.
|
|
347
|
+
|
|
295
348
|
The package is ESM-only and requires Node.js 20 or newer.
|
package/dist/control-plane.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
-
import { type DeploymentActivationRegistry } from './activation.js';
|
|
2
|
+
import { type DeploymentActivationRecord, type DeploymentActivationRegistry } from './activation.js';
|
|
3
3
|
import { type DeploymentControlPlaneLimits } from './control-plane-limits.js';
|
|
4
4
|
import type { DeploymentAuthenticator, DeploymentIntake, DeploymentIntakeRequest, DeploymentIntakeResponse } from './types.js';
|
|
5
5
|
export type DeploymentControlPlaneAction = 'activate' | 'read-current-activation' | 'read-activation-history';
|
|
@@ -29,6 +29,9 @@ export interface DeploymentControlPlaneOptions<Principal> {
|
|
|
29
29
|
readonly authenticator: DeploymentAuthenticator<Principal>;
|
|
30
30
|
readonly authorizer: DeploymentControlPlaneAuthorizer<Principal>;
|
|
31
31
|
readonly limits?: Partial<DeploymentControlPlaneLimits>;
|
|
32
|
+
/** Called after a successful activation has been durably committed. */
|
|
33
|
+
readonly onActivation?: (activation: DeploymentActivationRecord) => void | Promise<void>;
|
|
34
|
+
readonly onBackgroundError?: (error: unknown) => void;
|
|
32
35
|
}
|
|
33
36
|
export type DeploymentControlPlaneErrorCode = 'HQ_CONTROL_BAD_REQUEST' | 'HQ_CONTROL_UNAUTHENTICATED' | 'HQ_CONTROL_FORBIDDEN' | 'HQ_CONTROL_NOT_FOUND' | 'HQ_CONTROL_METHOD_NOT_ALLOWED' | 'HQ_CONTROL_TOO_LARGE' | 'HQ_CONTROL_RELEASE_NOT_FOUND' | 'HQ_CONTROL_RELEASE_UNAVAILABLE' | 'HQ_CONTROL_INTERNAL';
|
|
34
37
|
export declare function createDeploymentControlPlane<Principal>(options: DeploymentControlPlaneOptions<Principal>): DeploymentControlPlane;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../src/control-plane.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,
|
|
1
|
+
{"version":3,"file":"control-plane.d.ts","sourceRoot":"","sources":["../src/control-plane.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,4BAA4B,EAClC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,4BAA4B,EAClC,MAAM,2BAA2B,CAAC;AACnC,OAAO,KAAK,EACV,uBAAuB,EACvB,gBAAgB,EAChB,uBAAuB,EACvB,wBAAwB,EACzB,MAAM,YAAY,CAAC;AAMpB,MAAM,MAAM,4BAA4B,GACpC,UAAU,GACV,yBAAyB,GACzB,yBAAyB,CAAC;AAE9B,MAAM,WAAW,wCAAwC,CAAC,SAAS;IACjE,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,MAAM,EAAE,4BAA4B,CAAC;IAC9C,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,gCAAgC,CAAC,SAAS;IACzD,SAAS,CAAC,KAAK,EAAE,wCAAwC,CAAC,SAAS,CAAC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;CACzF;AAED,MAAM,WAAW,6BAA8B,SAAQ,uBAAuB;IAC5E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC;IAClF,sEAAsE;IACtE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,MAAM,MAAM,8BAA8B,GAAG,wBAAwB,CAAC;AAEtE,MAAM,WAAW,sBAAsB;IACrC,MAAM,CAAC,OAAO,EAAE,6BAA6B,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;CACzF;AAED,MAAM,WAAW,6BAA6B,CAAC,SAAS;IACtD,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;IAClC,QAAQ,CAAC,WAAW,EAAE,4BAA4B,CAAC;IACnD,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC,SAAS,CAAC,CAAC;IAC3D,QAAQ,CAAC,UAAU,EAAE,gCAAgC,CAAC,SAAS,CAAC,CAAC;IACjE,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC,4BAA4B,CAAC,CAAC;IACxD,uEAAuE;IACvE,QAAQ,CAAC,YAAY,CAAC,EAAE,CACtB,UAAU,EAAE,0BAA0B,KACnC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC1B,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,MAAM,+BAA+B,GACvC,wBAAwB,GACxB,4BAA4B,GAC5B,sBAAsB,GACtB,sBAAsB,GACtB,+BAA+B,GAC/B,sBAAsB,GACtB,8BAA8B,GAC9B,gCAAgC,GAChC,qBAAqB,CAAC;AA+X1B,wBAAgB,4BAA4B,CAAC,SAAS,EACpD,OAAO,EAAE,6BAA6B,CAAC,SAAS,CAAC,GAChD,sBAAsB,CAoIxB"}
|
package/dist/control-plane.js
CHANGED
|
@@ -269,6 +269,24 @@ function validatedHistoryQuery(query, maximum) {
|
|
|
269
269
|
}
|
|
270
270
|
export function createDeploymentControlPlane(options) {
|
|
271
271
|
const limits = resolveDeploymentControlPlaneLimits(options.limits);
|
|
272
|
+
function reportBackgroundError(error) {
|
|
273
|
+
try {
|
|
274
|
+
options.onBackgroundError?.(error);
|
|
275
|
+
}
|
|
276
|
+
catch {
|
|
277
|
+
// Diagnostics cannot change the result of an already-durable activation.
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
function notifyActivation(activation) {
|
|
281
|
+
try {
|
|
282
|
+
const operation = options.onActivation?.(activation);
|
|
283
|
+
if (operation)
|
|
284
|
+
void Promise.resolve(operation).catch(reportBackgroundError);
|
|
285
|
+
}
|
|
286
|
+
catch (error) {
|
|
287
|
+
reportBackgroundError(error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
272
290
|
async function authenticateAndAuthorize(request, action, target) {
|
|
273
291
|
throwIfAborted(request.signal);
|
|
274
292
|
const token = bearerToken(request.headers);
|
|
@@ -334,6 +352,7 @@ export function createDeploymentControlPlane(options) {
|
|
|
334
352
|
current: result.current,
|
|
335
353
|
});
|
|
336
354
|
}
|
|
355
|
+
notifyActivation(result.activation);
|
|
337
356
|
return activationResponse(result.status, result.activation);
|
|
338
357
|
}
|
|
339
358
|
requireMethod(method, 'GET');
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { type DeploymentDataPlane } from './data-plane.js';
|
|
3
|
+
export type DeploymentDataPlaneFetchHandler = (request: Request) => Promise<Response>;
|
|
4
|
+
export type DeploymentDataPlaneNodeHandler = (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
5
|
+
export interface DeploymentDataPlaneAdapterRequest {
|
|
6
|
+
readonly method: string;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
readonly query: Readonly<Record<string, string | readonly string[]>>;
|
|
9
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
10
|
+
}
|
|
11
|
+
export interface DeploymentDataPlaneAdapterOptions {
|
|
12
|
+
/** Request body limit from 1 through 1,048,576 bytes. */
|
|
13
|
+
readonly maxRequestBytes?: number;
|
|
14
|
+
readonly credentials?: (request: DeploymentDataPlaneAdapterRequest) => unknown;
|
|
15
|
+
}
|
|
16
|
+
export declare function createDeploymentDataPlaneFetchHandler(dataPlane: DeploymentDataPlane, options?: DeploymentDataPlaneAdapterOptions): DeploymentDataPlaneFetchHandler;
|
|
17
|
+
export declare function createDeploymentDataPlaneNodeHandler(dataPlane: DeploymentDataPlane, options?: DeploymentDataPlaneAdapterOptions): DeploymentDataPlaneNodeHandler;
|
|
18
|
+
//# sourceMappingURL=data-plane-adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-plane-adapters.d.ts","sourceRoot":"","sources":["../src/data-plane-adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AAIjE,OAAO,EAEL,KAAK,mBAAmB,EAEzB,MAAM,iBAAiB,CAAC;AAGzB,MAAM,MAAM,+BAA+B,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACtF,MAAM,MAAM,8BAA8B,GAAG,CAC3C,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;AAEnB,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,MAAM,EAAE,CAAC,CAAC,CAAC;IACrE,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACpD;AAED,MAAM,WAAW,iCAAiC;IAChD,yDAAyD;IACzD,QAAQ,CAAC,eAAe,CAAC,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,OAAO,EAAE,iCAAiC,KAAK,OAAO,CAAC;CAChF;AAoPD,wBAAgB,qCAAqC,CACnD,SAAS,EAAE,mBAAmB,EAC9B,OAAO,GAAE,iCAAsC,GAC9C,+BAA+B,CAwBjC;AAqDD,wBAAgB,oCAAoC,CAClD,SAAS,EAAE,mBAAmB,EAC9B,OAAO,GAAE,iCAAsC,GAC9C,8BAA8B,CAuChC"}
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
import { DEFAULT_PROTOCOL_SCHEMA_VALUE_LIMITS, } from '@hypequery/protocol';
|
|
2
|
+
import { DeploymentDataPlaneError, } from './data-plane.js';
|
|
3
|
+
import { DeploymentHostError } from './host.js';
|
|
4
|
+
class AdapterError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
status;
|
|
7
|
+
constructor(code, status, message) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
const JSON_CONTENT_TYPE = /^application\/json(?:;\s*charset=utf-8)?$/i;
|
|
14
|
+
function maximumBytes(input) {
|
|
15
|
+
const maximum = DEFAULT_PROTOCOL_SCHEMA_VALUE_LIMITS.maxInputBytes;
|
|
16
|
+
const value = input ?? maximum;
|
|
17
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > maximum) {
|
|
18
|
+
throw new RangeError(`maxRequestBytes must be an integer between 1 and ${maximum}`);
|
|
19
|
+
}
|
|
20
|
+
return value;
|
|
21
|
+
}
|
|
22
|
+
function queryParameters(search) {
|
|
23
|
+
const query = Object.create(null);
|
|
24
|
+
for (const [name, value] of search) {
|
|
25
|
+
const current = query[name];
|
|
26
|
+
if (current === undefined)
|
|
27
|
+
query[name] = value;
|
|
28
|
+
else if (typeof current === 'string')
|
|
29
|
+
query[name] = Object.freeze([current, value]);
|
|
30
|
+
else
|
|
31
|
+
query[name] = Object.freeze([...current, value]);
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze(query);
|
|
34
|
+
}
|
|
35
|
+
function contentLength(headers, maximum) {
|
|
36
|
+
const value = headers['content-length'];
|
|
37
|
+
if (value === undefined)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (!/^(?:0|[1-9][0-9]*)$/.test(value)) {
|
|
40
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Content-Length is invalid.');
|
|
41
|
+
}
|
|
42
|
+
const length = Number(value);
|
|
43
|
+
if (!Number.isSafeInteger(length)) {
|
|
44
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Content-Length is invalid.');
|
|
45
|
+
}
|
|
46
|
+
if (length > maximum) {
|
|
47
|
+
throw new AdapterError('HQ_DATA_PLANE_REQUEST_TOO_LARGE', 413, 'The data-plane request exceeds its byte limit.');
|
|
48
|
+
}
|
|
49
|
+
return length;
|
|
50
|
+
}
|
|
51
|
+
function requestInput(query, body, contentType) {
|
|
52
|
+
const hasQuery = Object.keys(query).length > 0;
|
|
53
|
+
if (body !== undefined && hasQuery) {
|
|
54
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'A data-plane request cannot contain both query parameters and a JSON body.');
|
|
55
|
+
}
|
|
56
|
+
if (body !== undefined) {
|
|
57
|
+
if (!contentType || !JSON_CONTENT_TYPE.test(contentType)) {
|
|
58
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Data-plane request bodies require application/json with UTF-8 encoding.');
|
|
59
|
+
}
|
|
60
|
+
return body;
|
|
61
|
+
}
|
|
62
|
+
return hasQuery ? JSON.stringify(query) : undefined;
|
|
63
|
+
}
|
|
64
|
+
function credentials(options, request) {
|
|
65
|
+
return options.credentials ? options.credentials(request) : request.headers.authorization;
|
|
66
|
+
}
|
|
67
|
+
function cacheControl(result) {
|
|
68
|
+
if (result.cacheTtlMs === undefined || result.cacheTtlMs <= 0)
|
|
69
|
+
return 'no-store';
|
|
70
|
+
return `public, max-age=${Math.floor(result.cacheTtlMs / 1_000)}`;
|
|
71
|
+
}
|
|
72
|
+
function successResponse(result) {
|
|
73
|
+
if (result.output === undefined) {
|
|
74
|
+
return Object.freeze({
|
|
75
|
+
status: 204,
|
|
76
|
+
headers: Object.freeze({ 'cache-control': cacheControl(result) }),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
const body = `${JSON.stringify(result.output)}\n`;
|
|
80
|
+
return Object.freeze({
|
|
81
|
+
status: 200,
|
|
82
|
+
headers: Object.freeze({
|
|
83
|
+
'content-type': 'application/json; charset=utf-8',
|
|
84
|
+
'content-length': String(Buffer.byteLength(body)),
|
|
85
|
+
'cache-control': cacheControl(result),
|
|
86
|
+
}),
|
|
87
|
+
body,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
function errorStatus(error) {
|
|
91
|
+
switch (error.code) {
|
|
92
|
+
case 'HQ_DATA_PLANE_INPUT_INVALID': return 400;
|
|
93
|
+
case 'HQ_DATA_PLANE_UNAUTHENTICATED': return 401;
|
|
94
|
+
case 'HQ_DATA_PLANE_FORBIDDEN':
|
|
95
|
+
case 'HQ_DATA_PLANE_TENANT_REQUIRED': return 403;
|
|
96
|
+
case 'HQ_DATA_PLANE_ROUTE_NOT_FOUND': return 404;
|
|
97
|
+
case 'HQ_DATA_PLANE_METHOD_NOT_ALLOWED': return 405;
|
|
98
|
+
case 'HQ_DATA_PLANE_ABORTED': return 499;
|
|
99
|
+
case 'HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE': return 503;
|
|
100
|
+
default: return 500;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function errorResponse(error) {
|
|
104
|
+
let status = 500;
|
|
105
|
+
let code = 'HQ_DATA_PLANE_INTERNAL';
|
|
106
|
+
let message = 'The deployment data-plane request could not be processed.';
|
|
107
|
+
let path;
|
|
108
|
+
if (error instanceof AdapterError) {
|
|
109
|
+
status = error.status;
|
|
110
|
+
code = error.code;
|
|
111
|
+
message = error.message;
|
|
112
|
+
}
|
|
113
|
+
else if (error instanceof DeploymentDataPlaneError) {
|
|
114
|
+
status = errorStatus(error);
|
|
115
|
+
code = error.code;
|
|
116
|
+
if (status < 500) {
|
|
117
|
+
message = error.message;
|
|
118
|
+
path = error.path;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
else if (error instanceof DeploymentHostError) {
|
|
122
|
+
code = error.code;
|
|
123
|
+
if (error.code === 'HQ_DEPLOYMENT_HOST_NOT_READY'
|
|
124
|
+
|| error.code === 'HQ_DEPLOYMENT_HOST_CLOSED') {
|
|
125
|
+
status = 503;
|
|
126
|
+
message = error.message;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
const body = `${JSON.stringify({
|
|
130
|
+
error: { code, message, ...(path === undefined ? {} : { path }) },
|
|
131
|
+
})}\n`;
|
|
132
|
+
return Object.freeze({
|
|
133
|
+
status,
|
|
134
|
+
headers: Object.freeze({
|
|
135
|
+
'content-type': 'application/json; charset=utf-8',
|
|
136
|
+
'content-length': String(Buffer.byteLength(body)),
|
|
137
|
+
'cache-control': 'no-store',
|
|
138
|
+
...(status === 401 ? { 'www-authenticate': 'Bearer' } : {}),
|
|
139
|
+
}),
|
|
140
|
+
body,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
async function execute(dataPlane, options, request, input, signal) {
|
|
144
|
+
try {
|
|
145
|
+
const result = await dataPlane.executeJson({
|
|
146
|
+
...request,
|
|
147
|
+
input,
|
|
148
|
+
credentials: credentials(options, request),
|
|
149
|
+
signal,
|
|
150
|
+
});
|
|
151
|
+
return successResponse(result);
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
return errorResponse(error);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function fetchHeaders(input) {
|
|
158
|
+
const headers = Object.create(null);
|
|
159
|
+
for (const [name, value] of input)
|
|
160
|
+
headers[name.toLowerCase()] = value;
|
|
161
|
+
if (headers.authorization?.includes(',')) {
|
|
162
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Authorization header is ambiguous.');
|
|
163
|
+
}
|
|
164
|
+
return Object.freeze(headers);
|
|
165
|
+
}
|
|
166
|
+
async function fetchBytes(stream, declared, maximum) {
|
|
167
|
+
if (stream === null) {
|
|
168
|
+
if (declared !== undefined && declared !== 0) {
|
|
169
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Request body length is invalid.');
|
|
170
|
+
}
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
const reader = stream.getReader();
|
|
174
|
+
const chunks = [];
|
|
175
|
+
let total = 0;
|
|
176
|
+
try {
|
|
177
|
+
while (true) {
|
|
178
|
+
const result = await reader.read();
|
|
179
|
+
if (result.done)
|
|
180
|
+
break;
|
|
181
|
+
total += result.value.byteLength;
|
|
182
|
+
if (total > maximum)
|
|
183
|
+
throw new AdapterError('HQ_DATA_PLANE_REQUEST_TOO_LARGE', 413, 'The data-plane request exceeds its byte limit.');
|
|
184
|
+
if (declared !== undefined && total > declared) {
|
|
185
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Request body length is invalid.');
|
|
186
|
+
}
|
|
187
|
+
chunks.push(result.value);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
catch (error) {
|
|
191
|
+
await reader.cancel(error).catch(() => undefined);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
finally {
|
|
195
|
+
reader.releaseLock();
|
|
196
|
+
}
|
|
197
|
+
if (declared !== undefined && total !== declared) {
|
|
198
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Request body length is invalid.');
|
|
199
|
+
}
|
|
200
|
+
const bytes = new Uint8Array(total);
|
|
201
|
+
let offset = 0;
|
|
202
|
+
for (const chunk of chunks) {
|
|
203
|
+
bytes.set(chunk, offset);
|
|
204
|
+
offset += chunk.byteLength;
|
|
205
|
+
}
|
|
206
|
+
return bytes.byteLength === 0 ? undefined : bytes;
|
|
207
|
+
}
|
|
208
|
+
export function createDeploymentDataPlaneFetchHandler(dataPlane, options = {}) {
|
|
209
|
+
const maximum = maximumBytes(options.maxRequestBytes);
|
|
210
|
+
return async (request) => {
|
|
211
|
+
let response;
|
|
212
|
+
try {
|
|
213
|
+
const url = new URL(request.url);
|
|
214
|
+
const headers = fetchHeaders(request.headers);
|
|
215
|
+
const query = queryParameters(url.searchParams);
|
|
216
|
+
const body = await fetchBytes(request.body, contentLength(headers, maximum), maximum);
|
|
217
|
+
const adapted = Object.freeze({
|
|
218
|
+
method: request.method.toUpperCase(), path: url.pathname, query, headers,
|
|
219
|
+
});
|
|
220
|
+
response = await execute(dataPlane, options, adapted, requestInput(query, body, headers['content-type']), request.signal);
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
response = errorResponse(error);
|
|
224
|
+
}
|
|
225
|
+
return new Response(response.body ?? null, { status: response.status, headers: response.headers });
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
function nodeHeaders(request) {
|
|
229
|
+
const headers = Object.create(null);
|
|
230
|
+
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
231
|
+
const name = request.rawHeaders[index].toLowerCase();
|
|
232
|
+
const value = request.rawHeaders[index + 1];
|
|
233
|
+
if (name === 'authorization' && headers[name] !== undefined) {
|
|
234
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Authorization header is ambiguous.');
|
|
235
|
+
}
|
|
236
|
+
headers[name] = headers[name] === undefined ? value : `${headers[name]}, ${value}`;
|
|
237
|
+
}
|
|
238
|
+
return Object.freeze(headers);
|
|
239
|
+
}
|
|
240
|
+
function nodeHasBody(headers) {
|
|
241
|
+
return headers['transfer-encoding'] !== undefined
|
|
242
|
+
|| headers['content-length'] !== undefined && headers['content-length'] !== '0';
|
|
243
|
+
}
|
|
244
|
+
async function nodeBytes(request, hasBody, declared, maximum) {
|
|
245
|
+
if (!hasBody)
|
|
246
|
+
return undefined;
|
|
247
|
+
const chunks = [];
|
|
248
|
+
let total = 0;
|
|
249
|
+
for await (const chunk of request) {
|
|
250
|
+
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
251
|
+
total += bytes.byteLength;
|
|
252
|
+
if (total > maximum)
|
|
253
|
+
throw new AdapterError('HQ_DATA_PLANE_REQUEST_TOO_LARGE', 413, 'The data-plane request exceeds its byte limit.');
|
|
254
|
+
if (declared !== undefined && total > declared) {
|
|
255
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Request body length is invalid.');
|
|
256
|
+
}
|
|
257
|
+
chunks.push(bytes);
|
|
258
|
+
}
|
|
259
|
+
if (declared !== undefined && total !== declared) {
|
|
260
|
+
throw new AdapterError('HQ_DATA_PLANE_INPUT_INVALID', 400, 'Request body length is invalid.');
|
|
261
|
+
}
|
|
262
|
+
return total === 0 ? undefined : Buffer.concat(chunks, total);
|
|
263
|
+
}
|
|
264
|
+
function sendNodeResponse(response, result) {
|
|
265
|
+
if (response.writableEnded || response.headersSent)
|
|
266
|
+
return;
|
|
267
|
+
response.statusCode = result.status;
|
|
268
|
+
for (const [name, value] of Object.entries(result.headers))
|
|
269
|
+
response.setHeader(name, value);
|
|
270
|
+
response.end(result.body);
|
|
271
|
+
}
|
|
272
|
+
export function createDeploymentDataPlaneNodeHandler(dataPlane, options = {}) {
|
|
273
|
+
const maximum = maximumBytes(options.maxRequestBytes);
|
|
274
|
+
return async (request, response) => {
|
|
275
|
+
const abort = new AbortController();
|
|
276
|
+
const onRequestClose = () => {
|
|
277
|
+
if (!request.complete)
|
|
278
|
+
abort.abort(new Error('The data-plane request was interrupted.'));
|
|
279
|
+
};
|
|
280
|
+
const onResponseClose = () => {
|
|
281
|
+
if (!response.writableEnded)
|
|
282
|
+
abort.abort(new Error('The data-plane response was interrupted.'));
|
|
283
|
+
};
|
|
284
|
+
request.once('close', onRequestClose);
|
|
285
|
+
response.once('close', onResponseClose);
|
|
286
|
+
try {
|
|
287
|
+
const url = new URL(request.url ?? '/', 'http://deployment-data-plane.invalid');
|
|
288
|
+
const headers = nodeHeaders(request);
|
|
289
|
+
const query = queryParameters(url.searchParams);
|
|
290
|
+
const body = await nodeBytes(request, nodeHasBody(headers), contentLength(headers, maximum), maximum);
|
|
291
|
+
const adapted = Object.freeze({
|
|
292
|
+
method: (request.method ?? 'GET').toUpperCase(), path: url.pathname, query, headers,
|
|
293
|
+
});
|
|
294
|
+
sendNodeResponse(response, await execute(dataPlane, options, adapted, requestInput(query, body, headers['content-type']), abort.signal));
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
sendNodeResponse(response, errorResponse(error));
|
|
298
|
+
}
|
|
299
|
+
finally {
|
|
300
|
+
request.off('close', onRequestClose);
|
|
301
|
+
response.off('close', onResponseClose);
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
}
|
package/dist/data-plane.d.ts
CHANGED
|
@@ -19,9 +19,14 @@ export interface DeploymentDataPlaneRequest {
|
|
|
19
19
|
readonly method: string;
|
|
20
20
|
readonly path: string;
|
|
21
21
|
readonly input?: unknown;
|
|
22
|
+
readonly query?: Readonly<Record<string, string | readonly string[]>>;
|
|
23
|
+
readonly headers?: Readonly<Record<string, string>>;
|
|
22
24
|
readonly credentials?: unknown;
|
|
23
25
|
readonly signal?: AbortSignal;
|
|
24
26
|
}
|
|
27
|
+
export type DeploymentDataPlaneJsonRequest = Omit<DeploymentDataPlaneRequest, 'input'> & {
|
|
28
|
+
readonly input?: string | Uint8Array;
|
|
29
|
+
};
|
|
25
30
|
export interface DeploymentDataPlaneAuthenticationInput {
|
|
26
31
|
readonly credentials: unknown;
|
|
27
32
|
readonly request: DeploymentDataPlaneRequest;
|
|
@@ -64,6 +69,7 @@ export interface DeploymentDataPlaneResult {
|
|
|
64
69
|
}
|
|
65
70
|
export interface DeploymentDataPlane {
|
|
66
71
|
execute(request: DeploymentDataPlaneRequest): Promise<DeploymentDataPlaneResult>;
|
|
72
|
+
executeJson(request: DeploymentDataPlaneJsonRequest): Promise<DeploymentDataPlaneResult>;
|
|
67
73
|
}
|
|
68
74
|
export interface DeploymentDataPlaneOptions {
|
|
69
75
|
readonly deployment: ProtocolDeploymentContract;
|
package/dist/data-plane.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"data-plane.d.ts","sourceRoot":"","sources":["../src/data-plane.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,0BAA0B,EAC1B,2BAA2B,EAE5B,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"data-plane.d.ts","sourceRoot":"","sources":["../src/data-plane.ts"],"names":[],"mappings":"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"}
|
package/dist/data-plane.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { createProtocolSchemaValueParser, ProtocolSchemaValueError, validateProtocolDeploymentContract, } from '@hypequery/protocol';
|
|
1
|
+
import { createProtocolSchemaValueParser, ProtocolSchemaValueError, ProtocolValueError, validateProtocolDeploymentContract, } from '@hypequery/protocol';
|
|
2
2
|
import { resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
|
|
3
3
|
export class DeploymentDataPlaneError extends Error {
|
|
4
4
|
code;
|
|
@@ -72,129 +72,138 @@ export function createDeploymentDataPlane(options) {
|
|
|
72
72
|
const limits = resolveDeploymentDataPlaneLimits(options.limits);
|
|
73
73
|
const routes = routeTable(deployment, limits);
|
|
74
74
|
const paths = new Set(deployment.queries.map(query => query.endpoint.path));
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
if (
|
|
80
|
-
|
|
81
|
-
throw dataPlaneError('HQ_DATA_PLANE_METHOD_NOT_ALLOWED', 'The deployment route does not support this method.');
|
|
82
|
-
}
|
|
83
|
-
throw dataPlaneError('HQ_DATA_PLANE_ROUTE_NOT_FOUND', 'The deployment route was not found.');
|
|
75
|
+
async function execute(request, json) {
|
|
76
|
+
throwIfAborted(request.signal);
|
|
77
|
+
const route = routes.get(`${request.method}\0${request.path}`);
|
|
78
|
+
if (!route) {
|
|
79
|
+
if (paths.has(request.path)) {
|
|
80
|
+
throw dataPlaneError('HQ_DATA_PLANE_METHOD_NOT_ALLOWED', 'The deployment route does not support this method.');
|
|
84
81
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
82
|
+
throw dataPlaneError('HQ_DATA_PLANE_ROUTE_NOT_FOUND', 'The deployment route was not found.');
|
|
83
|
+
}
|
|
84
|
+
const { query } = route;
|
|
85
|
+
let principal = null;
|
|
86
|
+
if (query.endpoint.access.kind === 'authenticated' && !options.authenticate) {
|
|
87
|
+
throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'An authenticator is required for this deployment route.');
|
|
88
|
+
}
|
|
89
|
+
if (options.authenticate
|
|
90
|
+
&& (query.endpoint.access.kind === 'authenticated' || request.credentials !== undefined)) {
|
|
91
|
+
try {
|
|
92
|
+
principal = await options.authenticate({ credentials: request.credentials, request, query });
|
|
89
93
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
try {
|
|
93
|
-
principal = await options.authenticate({ credentials: request.credentials, request, query });
|
|
94
|
-
}
|
|
95
|
-
catch (error) {
|
|
96
|
-
throw dataPlaneError('HQ_DATA_PLANE_UNAUTHENTICATED', 'Authentication failed.', error);
|
|
97
|
-
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
throw dataPlaneError('HQ_DATA_PLANE_UNAUTHENTICATED', 'Authentication failed.', error);
|
|
98
96
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
97
|
+
}
|
|
98
|
+
throwIfAborted(request.signal);
|
|
99
|
+
if (query.endpoint.access.kind === 'authenticated') {
|
|
100
|
+
if (!principal) {
|
|
101
|
+
throw dataPlaneError('HQ_DATA_PLANE_UNAUTHENTICATED', 'Authentication is required.');
|
|
102
|
+
}
|
|
103
|
+
if (missing(query.endpoint.access.roles, principal.roles).length > 0
|
|
104
|
+
|| missing(query.endpoint.access.scopes, principal.scopes).length > 0) {
|
|
105
|
+
throw dataPlaneError('HQ_DATA_PLANE_FORBIDDEN', 'The principal lacks required access.');
|
|
108
106
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
107
|
+
}
|
|
108
|
+
let tenant;
|
|
109
|
+
if (query.endpoint.tenant.kind !== 'not-required') {
|
|
110
|
+
if (!options.resolveTenant) {
|
|
111
|
+
if (query.endpoint.tenant.kind === 'required') {
|
|
112
|
+
throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'A tenant resolver is required for this deployment route.');
|
|
115
113
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
throw dataPlaneError('HQ_DATA_PLANE_FORBIDDEN', 'Tenant resolution failed.', error);
|
|
122
|
-
}
|
|
114
|
+
}
|
|
115
|
+
else {
|
|
116
|
+
try {
|
|
117
|
+
tenant = await options.resolveTenant({ principal, request, query });
|
|
123
118
|
}
|
|
124
|
-
|
|
125
|
-
throw dataPlaneError('
|
|
119
|
+
catch (error) {
|
|
120
|
+
throw dataPlaneError('HQ_DATA_PLANE_FORBIDDEN', 'Tenant resolution failed.', error);
|
|
126
121
|
}
|
|
127
122
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
try {
|
|
131
|
-
input = route.input.parse(request.input);
|
|
123
|
+
if (query.endpoint.tenant.kind === 'required' && (tenant === undefined || tenant === null)) {
|
|
124
|
+
throw dataPlaneError('HQ_DATA_PLANE_TENANT_REQUIRED', 'Tenant context is required.');
|
|
132
125
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
126
|
+
}
|
|
127
|
+
throwIfAborted(request.signal);
|
|
128
|
+
let input;
|
|
129
|
+
try {
|
|
130
|
+
if (json && request.input !== undefined) {
|
|
131
|
+
if (typeof route.input.parseJson !== 'function')
|
|
132
|
+
throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'The route input parser does not support JSON request bodies.');
|
|
133
|
+
input = route.input.parseJson(request.input);
|
|
138
134
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
try {
|
|
142
|
-
switch (query.implementation.kind) {
|
|
143
|
-
case 'semantic-plan':
|
|
144
|
-
if (!options.executeSemanticPlan)
|
|
145
|
-
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No semantic-plan executor is configured.');
|
|
146
|
-
output = await options.executeSemanticPlan({
|
|
147
|
-
...common,
|
|
148
|
-
implementation: query.implementation,
|
|
149
|
-
deployment,
|
|
150
|
-
});
|
|
151
|
-
break;
|
|
152
|
-
case 'compiled-sql':
|
|
153
|
-
if (!options.executeCompiledSql)
|
|
154
|
-
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No compiled SQL executor is configured.');
|
|
155
|
-
output = await options.executeCompiledSql({
|
|
156
|
-
...common,
|
|
157
|
-
implementation: query.implementation,
|
|
158
|
-
parameters: sqlParameters(query, input, tenant),
|
|
159
|
-
});
|
|
160
|
-
break;
|
|
161
|
-
case 'runtime-reference':
|
|
162
|
-
if (!options.executeRuntimeReference)
|
|
163
|
-
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No runtime-reference executor is configured.');
|
|
164
|
-
output = await options.executeRuntimeReference({
|
|
165
|
-
...common,
|
|
166
|
-
implementation: query.implementation,
|
|
167
|
-
});
|
|
168
|
-
break;
|
|
169
|
-
}
|
|
135
|
+
else {
|
|
136
|
+
input = route.input.parse(request.input);
|
|
170
137
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
throw dataPlaneError('HQ_DATA_PLANE_ABORTED', 'The data-plane request was aborted.', error);
|
|
176
|
-
}
|
|
177
|
-
throw dataPlaneError('HQ_DATA_PLANE_EXECUTION_FAILED', 'Deployment query execution failed.', error);
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
if (error instanceof ProtocolSchemaValueError || error instanceof ProtocolValueError) {
|
|
141
|
+
throw dataPlaneError('HQ_DATA_PLANE_INPUT_INVALID', 'The request input does not match the deployment schema.', error, error.path);
|
|
178
142
|
}
|
|
179
|
-
|
|
180
|
-
|
|
143
|
+
throw error;
|
|
144
|
+
}
|
|
145
|
+
const common = { query, input, principal, tenant, request, signal: request.signal };
|
|
146
|
+
let output;
|
|
147
|
+
try {
|
|
148
|
+
switch (query.implementation.kind) {
|
|
149
|
+
case 'semantic-plan':
|
|
150
|
+
if (!options.executeSemanticPlan)
|
|
151
|
+
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No semantic-plan executor is configured.');
|
|
152
|
+
output = await options.executeSemanticPlan({
|
|
153
|
+
...common,
|
|
154
|
+
implementation: query.implementation,
|
|
155
|
+
deployment,
|
|
156
|
+
});
|
|
157
|
+
break;
|
|
158
|
+
case 'compiled-sql':
|
|
159
|
+
if (!options.executeCompiledSql)
|
|
160
|
+
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No compiled SQL executor is configured.');
|
|
161
|
+
output = await options.executeCompiledSql({
|
|
162
|
+
...common,
|
|
163
|
+
implementation: query.implementation,
|
|
164
|
+
parameters: sqlParameters(query, input, tenant),
|
|
165
|
+
});
|
|
166
|
+
break;
|
|
167
|
+
case 'runtime-reference':
|
|
168
|
+
if (!options.executeRuntimeReference)
|
|
169
|
+
throw dataPlaneError('HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE', 'No runtime-reference executor is configured.');
|
|
170
|
+
output = await options.executeRuntimeReference({
|
|
171
|
+
...common,
|
|
172
|
+
implementation: query.implementation,
|
|
173
|
+
});
|
|
174
|
+
break;
|
|
181
175
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
}
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
if (error instanceof DeploymentDataPlaneError)
|
|
186
179
|
throw error;
|
|
180
|
+
if (request.signal?.aborted) {
|
|
181
|
+
throw dataPlaneError('HQ_DATA_PLANE_ABORTED', 'The data-plane request was aborted.', error);
|
|
182
|
+
}
|
|
183
|
+
throw dataPlaneError('HQ_DATA_PLANE_EXECUTION_FAILED', 'Deployment query execution failed.', error);
|
|
184
|
+
}
|
|
185
|
+
try {
|
|
186
|
+
output = route.output.parse(output);
|
|
187
|
+
}
|
|
188
|
+
catch (error) {
|
|
189
|
+
if (error instanceof ProtocolSchemaValueError) {
|
|
190
|
+
throw dataPlaneError('HQ_DATA_PLANE_OUTPUT_INVALID', 'The query output does not match the deployment schema.', error, error.path);
|
|
187
191
|
}
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
const publicCacheTtlMs = query.endpoint.access.kind === 'public'
|
|
195
|
+
&& query.endpoint.tenant.kind === 'not-required'
|
|
196
|
+
&& principal === null
|
|
197
|
+
? query.endpoint.cacheTtlMs
|
|
198
|
+
: undefined;
|
|
199
|
+
return Object.freeze({
|
|
200
|
+
query: query.name,
|
|
201
|
+
output,
|
|
202
|
+
...(publicCacheTtlMs === undefined ? {} : { cacheTtlMs: publicCacheTtlMs }),
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
return Object.freeze({
|
|
206
|
+
execute: (request) => execute(request, false),
|
|
207
|
+
executeJson: (request) => execute(request, true),
|
|
199
208
|
});
|
|
200
209
|
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
import { type DeploymentActivationRegistry } from './activation.js';
|
|
3
|
+
import { type DeploymentControlPlane, type DeploymentControlPlaneAuthorizer } from './control-plane.js';
|
|
4
|
+
import type { DeploymentControlPlaneLimits } from './control-plane-limits.js';
|
|
5
|
+
import { type FileSystemDeploymentSubmissionStore } from './filesystem-store.js';
|
|
6
|
+
import { type DeploymentHost, type DeploymentHostDataPlaneConfiguration, type DeploymentHostDataPlaneInput } from './host.js';
|
|
7
|
+
import type { DeploymentIntakeLimits } from './limits.js';
|
|
8
|
+
import { type NodeDeploymentRuntimeFactoryOptions } from './node-runtime-factory.js';
|
|
9
|
+
import { type DeploymentRuntimeMaterializer } from './runtime-materialization.js';
|
|
10
|
+
import { type DeploymentRuntimeFactory, type DeploymentRuntimeSupervisor } from './runtime-supervisor.js';
|
|
11
|
+
import type { DeploymentAuthenticator, DeploymentAuthorizer, DeploymentIntake } from './types.js';
|
|
12
|
+
export interface FileSystemDeploymentHostOptions<SubmissionPrincipal, ControlPrincipal> {
|
|
13
|
+
readonly directory: string;
|
|
14
|
+
readonly targets: readonly ProtocolDeploymentReleaseTarget[];
|
|
15
|
+
readonly intake: {
|
|
16
|
+
readonly authenticator: DeploymentAuthenticator<SubmissionPrincipal>;
|
|
17
|
+
readonly authorizer: DeploymentAuthorizer<SubmissionPrincipal>;
|
|
18
|
+
readonly limits?: Partial<DeploymentIntakeLimits>;
|
|
19
|
+
readonly temporaryDirectory?: string;
|
|
20
|
+
};
|
|
21
|
+
readonly controlPlane: {
|
|
22
|
+
readonly authenticator: DeploymentAuthenticator<ControlPrincipal>;
|
|
23
|
+
readonly authorizer: DeploymentControlPlaneAuthorizer<ControlPrincipal>;
|
|
24
|
+
readonly limits?: Partial<DeploymentControlPlaneLimits>;
|
|
25
|
+
};
|
|
26
|
+
readonly configureDataPlane: (input: DeploymentHostDataPlaneInput) => DeploymentHostDataPlaneConfiguration | Promise<DeploymentHostDataPlaneConfiguration>;
|
|
27
|
+
/** Defaults to the reference Node worker runtime factory. */
|
|
28
|
+
readonly runtimeFactory?: DeploymentRuntimeFactory;
|
|
29
|
+
readonly nodeRuntime?: NodeDeploymentRuntimeFactoryOptions;
|
|
30
|
+
readonly activationClock?: () => Date;
|
|
31
|
+
readonly maxMaterializationAttempts?: number;
|
|
32
|
+
readonly drainTimeoutMs?: number;
|
|
33
|
+
readonly maxReconcileAttempts?: number;
|
|
34
|
+
readonly maxHostStabilityAttempts?: number;
|
|
35
|
+
readonly onBackgroundError?: (error: unknown) => void;
|
|
36
|
+
}
|
|
37
|
+
export interface FileSystemDeploymentHost<SubmissionPrincipal> {
|
|
38
|
+
readonly store: FileSystemDeploymentSubmissionStore<SubmissionPrincipal>;
|
|
39
|
+
readonly activations: DeploymentActivationRegistry;
|
|
40
|
+
readonly intake: DeploymentIntake;
|
|
41
|
+
readonly controlPlane: DeploymentControlPlane;
|
|
42
|
+
readonly materializer: DeploymentRuntimeMaterializer;
|
|
43
|
+
readonly supervisor: DeploymentRuntimeSupervisor;
|
|
44
|
+
readonly host: DeploymentHost;
|
|
45
|
+
start(options?: {
|
|
46
|
+
readonly signal?: AbortSignal;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
close(): Promise<void>;
|
|
49
|
+
}
|
|
50
|
+
export declare function createFileSystemDeploymentHost<SubmissionPrincipal, ControlPrincipal>(options: FileSystemDeploymentHostOptions<SubmissionPrincipal, ControlPrincipal>): FileSystemDeploymentHost<SubmissionPrincipal>;
|
|
51
|
+
//# sourceMappingURL=filesystem-host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"filesystem-host.d.ts","sourceRoot":"","sources":["../src/filesystem-host.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { createFileSystemDeploymentActivationRegistry, } from './activation.js';
|
|
2
|
+
import { createDeploymentControlPlane, } from './control-plane.js';
|
|
3
|
+
import { createFileSystemDeploymentSubmissionStore, } from './filesystem-store.js';
|
|
4
|
+
import { createDeploymentHost, } from './host.js';
|
|
5
|
+
import { createDeploymentIntake } from './intake.js';
|
|
6
|
+
import { createNodeWorkerDeploymentRuntimeFactory, } from './node-runtime-factory.js';
|
|
7
|
+
import { createDeploymentRuntimeMaterializer, } from './runtime-materialization.js';
|
|
8
|
+
import { createDeploymentRuntimeSupervisor, } from './runtime-supervisor.js';
|
|
9
|
+
export function createFileSystemDeploymentHost(options) {
|
|
10
|
+
if (options.runtimeFactory !== undefined && options.nodeRuntime !== undefined) {
|
|
11
|
+
throw new TypeError('runtimeFactory and nodeRuntime cannot both be configured.');
|
|
12
|
+
}
|
|
13
|
+
const targets = Object.freeze([...options.targets]);
|
|
14
|
+
const store = createFileSystemDeploymentSubmissionStore({
|
|
15
|
+
directory: options.directory,
|
|
16
|
+
});
|
|
17
|
+
const activations = createFileSystemDeploymentActivationRegistry({
|
|
18
|
+
directory: options.directory,
|
|
19
|
+
releases: store,
|
|
20
|
+
clock: options.activationClock,
|
|
21
|
+
});
|
|
22
|
+
const intake = createDeploymentIntake({
|
|
23
|
+
...options.intake,
|
|
24
|
+
store,
|
|
25
|
+
});
|
|
26
|
+
const materializer = createDeploymentRuntimeMaterializer({
|
|
27
|
+
activations,
|
|
28
|
+
releases: store,
|
|
29
|
+
maxStabilityAttempts: options.maxMaterializationAttempts,
|
|
30
|
+
});
|
|
31
|
+
const runtimeFactory = options.runtimeFactory
|
|
32
|
+
?? createNodeWorkerDeploymentRuntimeFactory(options.nodeRuntime);
|
|
33
|
+
const supervisor = createDeploymentRuntimeSupervisor({
|
|
34
|
+
materializer,
|
|
35
|
+
factory: runtimeFactory,
|
|
36
|
+
drainTimeoutMs: options.drainTimeoutMs,
|
|
37
|
+
maxReconcileAttempts: options.maxReconcileAttempts,
|
|
38
|
+
onBackgroundError: options.onBackgroundError,
|
|
39
|
+
});
|
|
40
|
+
const host = createDeploymentHost({
|
|
41
|
+
supervisor,
|
|
42
|
+
configureDataPlane: options.configureDataPlane,
|
|
43
|
+
maxStabilityAttempts: options.maxHostStabilityAttempts,
|
|
44
|
+
onBackgroundError: options.onBackgroundError,
|
|
45
|
+
});
|
|
46
|
+
const controlPlane = createDeploymentControlPlane({
|
|
47
|
+
intake,
|
|
48
|
+
activations,
|
|
49
|
+
...options.controlPlane,
|
|
50
|
+
onActivation: activation => host.scheduleReconcile(activation.target),
|
|
51
|
+
onBackgroundError: options.onBackgroundError,
|
|
52
|
+
});
|
|
53
|
+
let startPromise;
|
|
54
|
+
const result = {
|
|
55
|
+
store,
|
|
56
|
+
activations,
|
|
57
|
+
intake,
|
|
58
|
+
controlPlane,
|
|
59
|
+
materializer,
|
|
60
|
+
supervisor,
|
|
61
|
+
host,
|
|
62
|
+
start(startOptions = {}) {
|
|
63
|
+
const pending = startPromise ??= host.start(targets, startOptions).then(() => undefined);
|
|
64
|
+
void pending.catch(() => {
|
|
65
|
+
if (startPromise === pending)
|
|
66
|
+
startPromise = undefined;
|
|
67
|
+
});
|
|
68
|
+
return pending;
|
|
69
|
+
},
|
|
70
|
+
close: () => host.close(),
|
|
71
|
+
};
|
|
72
|
+
return Object.freeze(result);
|
|
73
|
+
}
|
package/dist/host.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type ProtocolDeploymentContract, type ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
import { type DeploymentDataPlane, type DeploymentDataPlaneOptions, type DeploymentDataPlaneRequest, type DeploymentDataPlaneResult, type DeploymentRuntimeReferenceExecutionInput } from './data-plane.js';
|
|
3
|
+
import type { DeploymentRuntimeReconcileResult, DeploymentRuntimeStatus, DeploymentRuntimeSupervisor } from './runtime-supervisor.js';
|
|
4
|
+
export type DeploymentHostErrorCode = 'HQ_DEPLOYMENT_HOST_CONFIGURATION' | 'HQ_DEPLOYMENT_HOST_CLOSED' | 'HQ_DEPLOYMENT_HOST_NOT_READY' | 'HQ_DEPLOYMENT_HOST_RECONCILE_FAILED' | 'HQ_DEPLOYMENT_HOST_RECONCILE_UNSTABLE';
|
|
5
|
+
export declare class DeploymentHostError extends Error {
|
|
6
|
+
readonly code: DeploymentHostErrorCode;
|
|
7
|
+
constructor(code: DeploymentHostErrorCode, message: string, options?: {
|
|
8
|
+
readonly cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export interface DeploymentHostDataPlaneInput {
|
|
12
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
13
|
+
readonly status: DeploymentRuntimeStatus;
|
|
14
|
+
readonly deployment: ProtocolDeploymentContract;
|
|
15
|
+
}
|
|
16
|
+
export type DeploymentHostDataPlaneConfiguration = Omit<DeploymentDataPlaneOptions, 'deployment' | 'executeRuntimeReference'> & {
|
|
17
|
+
readonly runtimeArgument: (input: DeploymentRuntimeReferenceExecutionInput) => unknown;
|
|
18
|
+
};
|
|
19
|
+
export interface DeploymentHostOptions {
|
|
20
|
+
readonly supervisor: DeploymentRuntimeSupervisor;
|
|
21
|
+
readonly configureDataPlane: (input: DeploymentHostDataPlaneInput) => DeploymentHostDataPlaneConfiguration | Promise<DeploymentHostDataPlaneConfiguration>;
|
|
22
|
+
/** Generation-stability attempts from 1 through 16. */
|
|
23
|
+
readonly maxStabilityAttempts?: number;
|
|
24
|
+
readonly onBackgroundError?: (error: unknown) => void;
|
|
25
|
+
}
|
|
26
|
+
export interface DeploymentHost {
|
|
27
|
+
start(targets: readonly ProtocolDeploymentReleaseTarget[], options?: {
|
|
28
|
+
readonly signal?: AbortSignal;
|
|
29
|
+
}): Promise<readonly DeploymentRuntimeReconcileResult[]>;
|
|
30
|
+
reconcile(target: ProtocolDeploymentReleaseTarget, options?: {
|
|
31
|
+
readonly signal?: AbortSignal;
|
|
32
|
+
}): Promise<DeploymentRuntimeReconcileResult>;
|
|
33
|
+
scheduleReconcile(target: ProtocolDeploymentReleaseTarget): void;
|
|
34
|
+
execute(target: ProtocolDeploymentReleaseTarget, request: DeploymentDataPlaneRequest): Promise<DeploymentDataPlaneResult>;
|
|
35
|
+
dataPlane(target: ProtocolDeploymentReleaseTarget): DeploymentDataPlane;
|
|
36
|
+
status(target: ProtocolDeploymentReleaseTarget): DeploymentRuntimeStatus | undefined;
|
|
37
|
+
close(): Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
export declare function createDeploymentHost(options: DeploymentHostOptions): DeploymentHost;
|
|
40
|
+
//# sourceMappingURL=host.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,0BAA0B,EAC/B,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,KAAK,mBAAmB,EAExB,KAAK,0BAA0B,EAC/B,KAAK,0BAA0B,EAC/B,KAAK,yBAAyB,EAC9B,KAAK,wCAAwC,EAC9C,MAAM,iBAAiB,CAAC;AAEzB,OAAO,KAAK,EAEV,gCAAgC,EAChC,uBAAuB,EACvB,2BAA2B,EAC5B,MAAM,yBAAyB,CAAC;AAKjC,MAAM,MAAM,uBAAuB,GAC/B,kCAAkC,GAClC,2BAA2B,GAC3B,8BAA8B,GAC9B,qCAAqC,GACrC,uCAAuC,CAAC;AAE5C,qBAAa,mBAAoB,SAAQ,KAAK;IAC5C,QAAQ,CAAC,IAAI,EAAE,uBAAuB,CAAC;gBAGrC,IAAI,EAAE,uBAAuB,EAC7B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,MAAM,oCAAoC,GAAG,IAAI,CACrD,0BAA0B,EAC1B,YAAY,GAAG,yBAAyB,CACzC,GAAG;IACF,QAAQ,CAAC,eAAe,EAAE,CAAC,KAAK,EAAE,wCAAwC,KAAK,OAAO,CAAC;CACxF,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,QAAQ,CAAC,kBAAkB,EAAE,CAC3B,KAAK,EAAE,4BAA4B,KAChC,oCAAoC,GAAG,OAAO,CAAC,oCAAoC,CAAC,CAAC;IAC1F,uDAAuD;IACvD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,CACH,OAAO,EAAE,SAAS,+BAA+B,EAAE,EACnD,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,SAAS,gCAAgC,EAAE,CAAC,CAAC;IACxD,SAAS,CACP,MAAM,EAAE,+BAA+B,EACvC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC7C,iBAAiB,CAAC,MAAM,EAAE,+BAA+B,GAAG,IAAI,CAAC;IACjE,OAAO,CACL,MAAM,EAAE,+BAA+B,EACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,yBAAyB,CAAC,CAAC;IACtC,SAAS,CAAC,MAAM,EAAE,+BAA+B,GAAG,mBAAmB,CAAC;IACxE,MAAM,CAAC,MAAM,EAAE,+BAA+B,GAAG,uBAAuB,GAAG,SAAS,CAAC;IACrF,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAiDD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAmLnF"}
|
package/dist/host.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { validateProtocolDeploymentReleaseTarget, } from '@hypequery/protocol';
|
|
2
|
+
import { createDeploymentDataPlane, } from './data-plane.js';
|
|
3
|
+
import { createDeploymentRuntimeSupervisorExecutor } from './data-plane-runtime.js';
|
|
4
|
+
const DEFAULT_STABILITY_ATTEMPTS = 4;
|
|
5
|
+
const MAX_STABILITY_ATTEMPTS = 16;
|
|
6
|
+
export class DeploymentHostError extends Error {
|
|
7
|
+
code;
|
|
8
|
+
constructor(code, message, options = {}) {
|
|
9
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
10
|
+
this.name = 'DeploymentHostError';
|
|
11
|
+
this.code = code;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function hostError(code, message, cause) {
|
|
15
|
+
return new DeploymentHostError(code, message, { cause });
|
|
16
|
+
}
|
|
17
|
+
function targetKey(target) {
|
|
18
|
+
return JSON.stringify([target.project, target.environment]);
|
|
19
|
+
}
|
|
20
|
+
function target(input) {
|
|
21
|
+
try {
|
|
22
|
+
return validateProtocolDeploymentReleaseTarget(input);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
throw hostError('HQ_DEPLOYMENT_HOST_CONFIGURATION', 'The deployment host target is invalid.', error);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function stabilityAttempts(input) {
|
|
29
|
+
const value = input ?? DEFAULT_STABILITY_ATTEMPTS;
|
|
30
|
+
if (!Number.isSafeInteger(value) || value < 1 || value > MAX_STABILITY_ATTEMPTS) {
|
|
31
|
+
throw hostError('HQ_DEPLOYMENT_HOST_CONFIGURATION', `maxStabilityAttempts must be between 1 and ${MAX_STABILITY_ATTEMPTS}.`);
|
|
32
|
+
}
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function sameGeneration(left, right) {
|
|
36
|
+
return left?.status.activationRevision === right?.status.activationRevision;
|
|
37
|
+
}
|
|
38
|
+
export function createDeploymentHost(options) {
|
|
39
|
+
if (!options.supervisor || typeof options.configureDataPlane !== 'function') {
|
|
40
|
+
throw hostError('HQ_DEPLOYMENT_HOST_CONFIGURATION', 'A runtime supervisor and data-plane configurator are required.');
|
|
41
|
+
}
|
|
42
|
+
const maximumAttempts = stabilityAttempts(options.maxStabilityAttempts);
|
|
43
|
+
const active = new Map();
|
|
44
|
+
const updates = new Map();
|
|
45
|
+
const background = new Set();
|
|
46
|
+
let closed = false;
|
|
47
|
+
let closePromise;
|
|
48
|
+
function ensureOpen() {
|
|
49
|
+
if (closed)
|
|
50
|
+
throw hostError('HQ_DEPLOYMENT_HOST_CLOSED', 'The deployment host is closed.');
|
|
51
|
+
}
|
|
52
|
+
function serialize(key, operation) {
|
|
53
|
+
const previous = updates.get(key) ?? Promise.resolve();
|
|
54
|
+
const current = previous.catch(() => undefined).then(operation);
|
|
55
|
+
const tracked = current.finally(() => {
|
|
56
|
+
if (updates.get(key) === tracked)
|
|
57
|
+
updates.delete(key);
|
|
58
|
+
});
|
|
59
|
+
updates.set(key, tracked);
|
|
60
|
+
return tracked;
|
|
61
|
+
}
|
|
62
|
+
async function install(desiredTarget, signal) {
|
|
63
|
+
for (let attempt = 0; attempt < maximumAttempts; attempt += 1) {
|
|
64
|
+
ensureOpen();
|
|
65
|
+
const result = await options.supervisor.reconcile(desiredTarget, { signal });
|
|
66
|
+
ensureOpen();
|
|
67
|
+
if (result.status === 'deactivated' || result.status === 'no-active-release') {
|
|
68
|
+
active.delete(targetKey(desiredTarget));
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
const generation = options.supervisor.generation(desiredTarget);
|
|
72
|
+
if (!generation
|
|
73
|
+
|| generation.status.activationRevision !== result.runtime.activationRevision) {
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
let configuration;
|
|
77
|
+
try {
|
|
78
|
+
configuration = await options.configureDataPlane(Object.freeze({
|
|
79
|
+
target: desiredTarget,
|
|
80
|
+
status: generation.status,
|
|
81
|
+
deployment: generation.deployment,
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
throw hostError('HQ_DEPLOYMENT_HOST_RECONCILE_FAILED', 'The active deployment data plane could not be configured.', error);
|
|
86
|
+
}
|
|
87
|
+
ensureOpen();
|
|
88
|
+
let dataPlane;
|
|
89
|
+
try {
|
|
90
|
+
dataPlane = createDeploymentDataPlane({
|
|
91
|
+
...configuration,
|
|
92
|
+
deployment: generation.deployment,
|
|
93
|
+
executeRuntimeReference: createDeploymentRuntimeSupervisorExecutor({
|
|
94
|
+
supervisor: options.supervisor,
|
|
95
|
+
target: desiredTarget,
|
|
96
|
+
activationRevision: generation.status.activationRevision,
|
|
97
|
+
argument: configuration.runtimeArgument,
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
throw hostError('HQ_DEPLOYMENT_HOST_RECONCILE_FAILED', 'The active deployment data plane could not be constructed.', error);
|
|
103
|
+
}
|
|
104
|
+
const confirmed = options.supervisor.generation(desiredTarget);
|
|
105
|
+
if (!sameGeneration(generation, confirmed))
|
|
106
|
+
continue;
|
|
107
|
+
active.set(targetKey(desiredTarget), Object.freeze({ generation, dataPlane }));
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
throw hostError('HQ_DEPLOYMENT_HOST_RECONCILE_UNSTABLE', 'The active deployment generation changed repeatedly during host reconciliation.');
|
|
111
|
+
}
|
|
112
|
+
const host = {
|
|
113
|
+
async start(targets, startOptions = {}) {
|
|
114
|
+
ensureOpen();
|
|
115
|
+
const validated = targets.map(target);
|
|
116
|
+
const keys = validated.map(targetKey);
|
|
117
|
+
if (new Set(keys).size !== keys.length) {
|
|
118
|
+
throw hostError('HQ_DEPLOYMENT_HOST_CONFIGURATION', 'Deployment host startup targets must be unique.');
|
|
119
|
+
}
|
|
120
|
+
return Object.freeze(await Promise.all(validated.map(candidate => (host.reconcile(candidate, startOptions)))));
|
|
121
|
+
},
|
|
122
|
+
reconcile(input, reconcileOptions = {}) {
|
|
123
|
+
ensureOpen();
|
|
124
|
+
const desiredTarget = target(input);
|
|
125
|
+
return serialize(targetKey(desiredTarget), () => install(desiredTarget, reconcileOptions.signal));
|
|
126
|
+
},
|
|
127
|
+
scheduleReconcile(input) {
|
|
128
|
+
ensureOpen();
|
|
129
|
+
const operation = host.reconcile(input).then(() => undefined).catch(error => {
|
|
130
|
+
try {
|
|
131
|
+
options.onBackgroundError?.(error);
|
|
132
|
+
}
|
|
133
|
+
catch {
|
|
134
|
+
// A diagnostic callback cannot make reconciliation an unhandled rejection.
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
background.add(operation);
|
|
138
|
+
void operation.finally(() => background.delete(operation)).catch(() => undefined);
|
|
139
|
+
},
|
|
140
|
+
async execute(input, request) {
|
|
141
|
+
ensureOpen();
|
|
142
|
+
const desiredTarget = target(input);
|
|
143
|
+
const hosted = active.get(targetKey(desiredTarget));
|
|
144
|
+
if (!hosted) {
|
|
145
|
+
throw hostError('HQ_DEPLOYMENT_HOST_NOT_READY', 'No deployment data plane is ready for target.');
|
|
146
|
+
}
|
|
147
|
+
return await hosted.dataPlane.execute(request);
|
|
148
|
+
},
|
|
149
|
+
dataPlane(input) {
|
|
150
|
+
ensureOpen();
|
|
151
|
+
const desiredTarget = target(input);
|
|
152
|
+
return Object.freeze({
|
|
153
|
+
execute: (request) => host.execute(desiredTarget, request),
|
|
154
|
+
executeJson: (request) => {
|
|
155
|
+
ensureOpen();
|
|
156
|
+
const hosted = active.get(targetKey(desiredTarget));
|
|
157
|
+
if (!hosted) {
|
|
158
|
+
throw hostError('HQ_DEPLOYMENT_HOST_NOT_READY', 'No deployment data plane is ready for target.');
|
|
159
|
+
}
|
|
160
|
+
return hosted.dataPlane.executeJson(request);
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
},
|
|
164
|
+
status(input) {
|
|
165
|
+
ensureOpen();
|
|
166
|
+
const desiredTarget = target(input);
|
|
167
|
+
return active.get(targetKey(desiredTarget))?.generation.status;
|
|
168
|
+
},
|
|
169
|
+
close() {
|
|
170
|
+
if (closePromise)
|
|
171
|
+
return closePromise;
|
|
172
|
+
closed = true;
|
|
173
|
+
closePromise = (async () => {
|
|
174
|
+
await Promise.allSettled([...updates.values()]);
|
|
175
|
+
await Promise.allSettled([...background]);
|
|
176
|
+
active.clear();
|
|
177
|
+
await options.supervisor.close();
|
|
178
|
+
})();
|
|
179
|
+
return closePromise;
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
return Object.freeze(host);
|
|
183
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -20,12 +20,18 @@ export type { NodeDeploymentRuntimeErrorCode, NodeDeploymentRuntimeFactoryOption
|
|
|
20
20
|
export { createDeploymentRuntimeMaterializer, DeploymentRuntimeMaterializationError, } from './runtime-materialization.js';
|
|
21
21
|
export type { DeploymentRuntimeArtifactSnapshot, DeploymentRuntimeMaterializationErrorCode, DeploymentRuntimeMaterializer, DeploymentRuntimeMaterializerOptions, DeploymentRuntimeQueryBinding, DeploymentRuntimeRelease, DeploymentRuntimeReleaseReader, DeploymentRuntimeSnapshot, } from './runtime-materialization.js';
|
|
22
22
|
export { createDeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorError, } from './runtime-supervisor.js';
|
|
23
|
-
export type { DeploymentRuntimeFactory, DeploymentRuntimeInstance, DeploymentRuntimeInstanceInvocation, DeploymentRuntimeInvocation, DeploymentRuntimeReconcileResult, DeploymentRuntimeStatus, DeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorErrorCode, DeploymentRuntimeSupervisorOptions, } from './runtime-supervisor.js';
|
|
23
|
+
export type { DeploymentRuntimeFactory, DeploymentRuntimeGeneration, DeploymentRuntimeInstance, DeploymentRuntimeInstanceInvocation, DeploymentRuntimeInvocation, DeploymentRuntimeReconcileResult, DeploymentRuntimeStatus, DeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorErrorCode, DeploymentRuntimeSupervisorOptions, } from './runtime-supervisor.js';
|
|
24
24
|
export type { DeploymentAuthenticationInput, DeploymentAuthenticator, DeploymentAuthorizationInput, DeploymentAuthorizer, DeploymentIntake, DeploymentIntakeOptions, DeploymentIntakeRequest, DeploymentIntakeResponse, DeploymentSubmissionResponse, DeploymentSubmissionStore, VerifiedDeploymentSubmission, } from './types.js';
|
|
25
25
|
export { createDeploymentDataPlane, DeploymentDataPlaneError, } from './data-plane.js';
|
|
26
|
-
export type { DeploymentCompiledSqlExecutionInput, DeploymentDataPlane, DeploymentDataPlaneAuthenticationInput, DeploymentDataPlaneErrorCode, DeploymentDataPlaneExecutionInput, DeploymentDataPlaneOptions, DeploymentDataPlanePrincipal, DeploymentDataPlaneRequest, DeploymentDataPlaneResult, DeploymentDataPlaneTenantInput, DeploymentRuntimeReferenceExecutionInput, DeploymentSemanticPlanExecutionInput, } from './data-plane.js';
|
|
26
|
+
export type { DeploymentCompiledSqlExecutionInput, DeploymentDataPlane, DeploymentDataPlaneAuthenticationInput, DeploymentDataPlaneErrorCode, DeploymentDataPlaneExecutionInput, DeploymentDataPlaneJsonRequest, DeploymentDataPlaneOptions, DeploymentDataPlanePrincipal, DeploymentDataPlaneRequest, DeploymentDataPlaneResult, DeploymentDataPlaneTenantInput, DeploymentRuntimeReferenceExecutionInput, DeploymentSemanticPlanExecutionInput, } from './data-plane.js';
|
|
27
27
|
export { DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS, resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
|
|
28
28
|
export type { DeploymentDataPlaneLimits } from './data-plane-limits.js';
|
|
29
29
|
export { createDeploymentRuntimeSupervisorExecutor } from './data-plane-runtime.js';
|
|
30
30
|
export type { DeploymentRuntimeSupervisorExecutorOptions } from './data-plane-runtime.js';
|
|
31
|
+
export { createDeploymentDataPlaneFetchHandler, createDeploymentDataPlaneNodeHandler, } from './data-plane-adapters.js';
|
|
32
|
+
export type { DeploymentDataPlaneAdapterOptions, DeploymentDataPlaneAdapterRequest, DeploymentDataPlaneFetchHandler, DeploymentDataPlaneNodeHandler, } from './data-plane-adapters.js';
|
|
33
|
+
export { createDeploymentHost, DeploymentHostError, } from './host.js';
|
|
34
|
+
export { createFileSystemDeploymentHost } from './filesystem-host.js';
|
|
35
|
+
export type { FileSystemDeploymentHost, FileSystemDeploymentHostOptions, } from './filesystem-host.js';
|
|
36
|
+
export type { DeploymentHost, DeploymentHostDataPlaneConfiguration, DeploymentHostDataPlaneInput, DeploymentHostErrorCode, DeploymentHostOptions, } from './host.js';
|
|
31
37
|
//# 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":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -13,3 +13,6 @@ export { createDeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorError, }
|
|
|
13
13
|
export { createDeploymentDataPlane, DeploymentDataPlaneError, } from './data-plane.js';
|
|
14
14
|
export { DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS, resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
|
|
15
15
|
export { createDeploymentRuntimeSupervisorExecutor } from './data-plane-runtime.js';
|
|
16
|
+
export { createDeploymentDataPlaneFetchHandler, createDeploymentDataPlaneNodeHandler, } from './data-plane-adapters.js';
|
|
17
|
+
export { createDeploymentHost, DeploymentHostError, } from './host.js';
|
|
18
|
+
export { createFileSystemDeploymentHost } from './filesystem-host.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
1
|
+
import type { ProtocolDeploymentContract, ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
2
|
import type { DeploymentRuntimeMaterializer, DeploymentRuntimeQueryBinding, DeploymentRuntimeSnapshot } from './runtime-materialization.js';
|
|
3
3
|
export type DeploymentRuntimeSupervisorErrorCode = 'HQ_RUNTIME_SUPERVISOR_CONFIGURATION' | 'HQ_RUNTIME_SUPERVISOR_CLOSED' | 'HQ_RUNTIME_NOT_READY' | 'HQ_RUNTIME_QUERY_NOT_FOUND' | 'HQ_RUNTIME_QUERY_NOT_EXECUTABLE' | 'HQ_RUNTIME_START_FAILED' | 'HQ_RUNTIME_HEALTH_FAILED' | 'HQ_RUNTIME_INVOCATION_FAILED' | 'HQ_RUNTIME_RECONCILE_UNSTABLE' | 'HQ_RUNTIME_ABORTED';
|
|
4
4
|
export declare class DeploymentRuntimeSupervisorError extends Error {
|
|
@@ -39,6 +39,10 @@ export interface DeploymentRuntimeStatus {
|
|
|
39
39
|
readonly releaseIdentity: string;
|
|
40
40
|
readonly bundleIdentity: string;
|
|
41
41
|
}
|
|
42
|
+
export interface DeploymentRuntimeGeneration {
|
|
43
|
+
readonly status: DeploymentRuntimeStatus;
|
|
44
|
+
readonly deployment: ProtocolDeploymentContract;
|
|
45
|
+
}
|
|
42
46
|
export type DeploymentRuntimeReconcileResult = {
|
|
43
47
|
readonly status: 'activated';
|
|
44
48
|
readonly runtime: DeploymentRuntimeStatus;
|
|
@@ -57,6 +61,7 @@ export interface DeploymentRuntimeSupervisor {
|
|
|
57
61
|
}): Promise<DeploymentRuntimeReconcileResult>;
|
|
58
62
|
invoke(input: DeploymentRuntimeInvocation): Promise<unknown>;
|
|
59
63
|
status(target: ProtocolDeploymentReleaseTarget): DeploymentRuntimeStatus | undefined;
|
|
64
|
+
generation(target: ProtocolDeploymentReleaseTarget): DeploymentRuntimeGeneration | undefined;
|
|
60
65
|
close(): Promise<void>;
|
|
61
66
|
}
|
|
62
67
|
export interface DeploymentRuntimeSupervisorOptions {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"runtime-supervisor.d.ts","sourceRoot":"","sources":["../src/runtime-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"runtime-supervisor.d.ts","sourceRoot":"","sources":["../src/runtime-supervisor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAC1B,+BAA+B,EAChC,MAAM,qBAAqB,CAAC;AAC7B,OAAO,KAAK,EACV,6BAA6B,EAC7B,6BAA6B,EAC7B,yBAAyB,EAC1B,MAAM,8BAA8B,CAAC;AAOtC,MAAM,MAAM,oCAAoC,GAC5C,qCAAqC,GACrC,8BAA8B,GAC9B,sBAAsB,GACtB,4BAA4B,GAC5B,iCAAiC,GACjC,yBAAyB,GACzB,0BAA0B,GAC1B,8BAA8B,GAC9B,+BAA+B,GAC/B,oBAAoB,CAAC;AAEzB,qBAAa,gCAAiC,SAAQ,KAAK;IACzD,QAAQ,CAAC,IAAI,EAAE,oCAAoC,CAAC;gBAGlD,IAAI,EAAE,oCAAoC,EAC1C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,6BAA6B,CAAC;IAChD,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,yBAAyB;IACxC,WAAW,CAAC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACrE,MAAM,CAAC,KAAK,EAAE,mCAAmC,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACrE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,wBAAwB;IACvC,KAAK,CACH,QAAQ,EAAE,yBAAyB,EACnC,KAAK,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GACvC,OAAO,CAAC,yBAAyB,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,+EAA+E;IAC/E,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;CACjD;AAED,MAAM,MAAM,gCAAgC,GACxC;IAAE,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAA;CAAE,GAC3E;IAAE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,uBAAuB,CAAA;CAAE,GACjF;IAAE,QAAQ,CAAC,MAAM,EAAE,aAAa,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAA;CAAE,GAC9E;IAAE,QAAQ,CAAC,MAAM,EAAE,mBAAmB,CAAA;CAAE,CAAC;AAE7C,MAAM,WAAW,2BAA2B;IAC1C,SAAS,CACP,MAAM,EAAE,+BAA+B,EACvC,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAA;KAAE,GAC1C,OAAO,CAAC,gCAAgC,CAAC,CAAC;IAC7C,MAAM,CAAC,KAAK,EAAE,2BAA2B,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,MAAM,CAAC,MAAM,EAAE,+BAA+B,GAAG,uBAAuB,GAAG,SAAS,CAAC;IACrF,UAAU,CAAC,MAAM,EAAE,+BAA+B,GAAG,2BAA2B,GAAG,SAAS,CAAC;IAC7F,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,kCAAkC;IACjD,QAAQ,CAAC,YAAY,EAAE,6BAA6B,CAAC;IACrD,QAAQ,CAAC,OAAO,EAAE,wBAAwB,CAAC;IAC3C,oEAAoE;IACpE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,uDAAuD;IACvD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;IACvC,QAAQ,CAAC,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACvD;AAsFD,wBAAgB,iCAAiC,CAC/C,OAAO,EAAE,kCAAkC,GAC1C,2BAA2B,CA6P7B"}
|
|
@@ -232,6 +232,13 @@ export function createDeploymentRuntimeSupervisor(options) {
|
|
|
232
232
|
const generation = active.get(targetKey(target));
|
|
233
233
|
return generation ? status(generation) : undefined;
|
|
234
234
|
},
|
|
235
|
+
generation(target) {
|
|
236
|
+
const generation = active.get(targetKey(target));
|
|
237
|
+
return generation ? Object.freeze({
|
|
238
|
+
status: status(generation),
|
|
239
|
+
deployment: generation.snapshot.deployment,
|
|
240
|
+
}) : undefined;
|
|
241
|
+
},
|
|
235
242
|
close() {
|
|
236
243
|
if (closePromise)
|
|
237
244
|
return closePromise;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/deployment",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Provider-neutral deployment verification and intake for Hypequery",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"README.md"
|
|
19
19
|
],
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@hypequery/protocol": "0.
|
|
21
|
+
"@hypequery/protocol": "0.8.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
24
|
"@types/node": "^22.5.0",
|