@hypequery/deployment 0.2.0 → 0.4.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 +142 -2
- package/dist/activation.d.ts +11 -0
- package/dist/activation.d.ts.map +1 -1
- package/dist/activation.js +100 -19
- package/dist/control-plane-adapters.d.ts +7 -0
- package/dist/control-plane-adapters.d.ts.map +1 -0
- package/dist/control-plane-adapters.js +163 -0
- package/dist/control-plane-limits.d.ts +7 -0
- package/dist/control-plane-limits.d.ts.map +1 -0
- package/dist/control-plane-limits.js +18 -0
- package/dist/control-plane.d.ts +35 -0
- package/dist/control-plane.d.ts.map +1 -0
- package/dist/control-plane.js +362 -0
- package/dist/data-plane-limits.d.ts +5 -0
- package/dist/data-plane-limits.d.ts.map +1 -0
- package/dist/data-plane-limits.js +5 -0
- package/dist/data-plane-runtime.d.ts +13 -0
- package/dist/data-plane-runtime.d.ts.map +1 -0
- package/dist/data-plane-runtime.js +12 -0
- package/dist/data-plane.d.ts +78 -0
- package/dist/data-plane.d.ts.map +1 -0
- package/dist/data-plane.js +200 -0
- package/dist/index.d.ts +20 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +10 -1
- package/dist/node-runtime-factory.d.ts +16 -0
- package/dist/node-runtime-factory.d.ts.map +1 -0
- package/dist/node-runtime-factory.js +274 -0
- package/dist/runtime-materialization.d.ts +58 -0
- package/dist/runtime-materialization.d.ts.map +1 -0
- package/dist/runtime-materialization.js +187 -0
- package/dist/runtime-supervisor.d.ts +72 -0
- package/dist/runtime-supervisor.d.ts.map +1 -0
- package/dist/runtime-supervisor.js +258 -0
- package/package.json +3 -3
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
import { validateProtocolDeploymentReleaseTarget, } from '@hypequery/protocol';
|
|
2
|
+
import { DeploymentActivationError, } from './activation.js';
|
|
3
|
+
import { resolveDeploymentControlPlaneLimits, } from './control-plane-limits.js';
|
|
4
|
+
const IDENTITY_PATTERN = /^[0-9a-f]{64}$/;
|
|
5
|
+
const JSON_CONTENT_TYPE = /^application\/json(?:;\s*charset=utf-8)?$/i;
|
|
6
|
+
const textDecoder = new TextDecoder('utf-8', { fatal: true });
|
|
7
|
+
const ERROR_STATUS = Object.freeze({
|
|
8
|
+
HQ_CONTROL_BAD_REQUEST: 400,
|
|
9
|
+
HQ_CONTROL_UNAUTHENTICATED: 401,
|
|
10
|
+
HQ_CONTROL_FORBIDDEN: 403,
|
|
11
|
+
HQ_CONTROL_NOT_FOUND: 404,
|
|
12
|
+
HQ_CONTROL_METHOD_NOT_ALLOWED: 405,
|
|
13
|
+
HQ_CONTROL_TOO_LARGE: 413,
|
|
14
|
+
HQ_CONTROL_RELEASE_NOT_FOUND: 404,
|
|
15
|
+
HQ_CONTROL_RELEASE_UNAVAILABLE: 503,
|
|
16
|
+
HQ_CONTROL_INTERNAL: 500,
|
|
17
|
+
});
|
|
18
|
+
class ControlPlaneError extends Error {
|
|
19
|
+
code;
|
|
20
|
+
status;
|
|
21
|
+
expose;
|
|
22
|
+
headers;
|
|
23
|
+
constructor(code, message, options = {}) {
|
|
24
|
+
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
25
|
+
this.name = 'DeploymentControlPlaneError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
this.status = ERROR_STATUS[code];
|
|
28
|
+
this.expose = options.expose ?? this.status < 500;
|
|
29
|
+
this.headers = Object.freeze({ ...options.headers });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function requestHeader(headers, expectedName) {
|
|
33
|
+
const values = Object.entries(headers)
|
|
34
|
+
.filter(([name, value]) => value !== undefined && name.toLowerCase() === expectedName)
|
|
35
|
+
.map(([, value]) => value);
|
|
36
|
+
if (values.length > 1) {
|
|
37
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', `Duplicate ${expectedName} header.`);
|
|
38
|
+
}
|
|
39
|
+
return values[0];
|
|
40
|
+
}
|
|
41
|
+
function bearerToken(headers) {
|
|
42
|
+
const authorization = requestHeader(headers, 'authorization');
|
|
43
|
+
const token = authorization?.startsWith('Bearer ') ? authorization.slice(7) : '';
|
|
44
|
+
if (token.length < 1 || token.length > 4096 || token.trim() !== token
|
|
45
|
+
|| [...token].some(character => {
|
|
46
|
+
const code = character.charCodeAt(0);
|
|
47
|
+
return code < 0x21 || code > 0x7e;
|
|
48
|
+
})) {
|
|
49
|
+
throw new ControlPlaneError('HQ_CONTROL_UNAUTHENTICATED', 'A valid bearer credential is required.', { headers: { 'www-authenticate': 'Bearer' } });
|
|
50
|
+
}
|
|
51
|
+
return token;
|
|
52
|
+
}
|
|
53
|
+
function jsonResponse(status, body, headers = {}) {
|
|
54
|
+
const encoded = `${JSON.stringify(body)}\n`;
|
|
55
|
+
return Object.freeze({
|
|
56
|
+
status,
|
|
57
|
+
headers: Object.freeze({
|
|
58
|
+
'content-type': 'application/json; charset=utf-8',
|
|
59
|
+
'content-length': String(Buffer.byteLength(encoded)),
|
|
60
|
+
'cache-control': 'no-store',
|
|
61
|
+
...headers,
|
|
62
|
+
}),
|
|
63
|
+
body: encoded,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
function sanitizeMessage(input) {
|
|
67
|
+
return [...input].map(character => {
|
|
68
|
+
const code = character.codePointAt(0) ?? 0;
|
|
69
|
+
return code < 0x20 || code === 0x7f ? ' ' : character;
|
|
70
|
+
}).join('').slice(0, 1024).trim() || 'The deployment control-plane request was rejected.';
|
|
71
|
+
}
|
|
72
|
+
function errorResponse(error) {
|
|
73
|
+
const value = error instanceof ControlPlaneError
|
|
74
|
+
? error
|
|
75
|
+
: new ControlPlaneError('HQ_CONTROL_INTERNAL', 'The deployment control-plane request could not be processed.', { expose: false, cause: error });
|
|
76
|
+
return jsonResponse(value.status, {
|
|
77
|
+
error: {
|
|
78
|
+
code: value.code,
|
|
79
|
+
message: sanitizeMessage(value.expose
|
|
80
|
+
? value.message
|
|
81
|
+
: 'The deployment control-plane request could not be processed.'),
|
|
82
|
+
},
|
|
83
|
+
}, value.headers);
|
|
84
|
+
}
|
|
85
|
+
function decodeTargetSegment(input) {
|
|
86
|
+
try {
|
|
87
|
+
return decodeURIComponent(input);
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'Deployment target path encoding is invalid.', { cause: error });
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function validatedTarget(project, environment) {
|
|
94
|
+
try {
|
|
95
|
+
return validateProtocolDeploymentReleaseTarget({
|
|
96
|
+
project: decodeTargetSegment(project),
|
|
97
|
+
environment: decodeTargetSegment(environment),
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
catch (error) {
|
|
101
|
+
if (error instanceof ControlPlaneError)
|
|
102
|
+
throw error;
|
|
103
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'Deployment target path is invalid.', { cause: error });
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function parseRoute(path) {
|
|
107
|
+
if (path === '/v1/deployments/submissions')
|
|
108
|
+
return Object.freeze({ kind: 'submission' });
|
|
109
|
+
const segments = path.split('/');
|
|
110
|
+
if (segments.length !== 7 || segments[0] !== '' || segments[1] !== 'v1'
|
|
111
|
+
|| segments[2] !== 'deployments' || segments[3] !== 'targets')
|
|
112
|
+
return undefined;
|
|
113
|
+
if (segments[6] !== 'activation' && segments[6] !== 'activations')
|
|
114
|
+
return undefined;
|
|
115
|
+
const target = validatedTarget(segments[4], segments[5]);
|
|
116
|
+
if (segments[6] === 'activation')
|
|
117
|
+
return Object.freeze({ kind: 'activation', target });
|
|
118
|
+
if (segments[6] === 'activations')
|
|
119
|
+
return Object.freeze({ kind: 'history', target });
|
|
120
|
+
return undefined;
|
|
121
|
+
}
|
|
122
|
+
function requireMethod(method, ...expected) {
|
|
123
|
+
if (!expected.includes(method)) {
|
|
124
|
+
const allowed = expected.join(', ');
|
|
125
|
+
throw new ControlPlaneError('HQ_CONTROL_METHOD_NOT_ALLOWED', `This deployment control-plane route requires ${allowed}.`, { headers: { allow: allowed } });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function requireNoQuery(request) {
|
|
129
|
+
if (request.query && Object.keys(request.query).length > 0) {
|
|
130
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'This deployment control-plane route does not accept query parameters.');
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
function requireNoBody(request) {
|
|
134
|
+
const length = requestHeader(request.headers, 'content-length');
|
|
135
|
+
const transferEncoding = requestHeader(request.headers, 'transfer-encoding');
|
|
136
|
+
if (request.hasBody || transferEncoding !== undefined || (length !== undefined && length !== '0')) {
|
|
137
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'This deployment control-plane route does not accept a request body.');
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
function throwIfAborted(signal) {
|
|
141
|
+
if (signal?.aborted) {
|
|
142
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The deployment control-plane request was aborted.', { cause: signal.reason });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function readBoundedBody(request, maximum) {
|
|
146
|
+
const declared = requestHeader(request.headers, 'content-length');
|
|
147
|
+
let declaredLength;
|
|
148
|
+
if (declared !== undefined) {
|
|
149
|
+
if (!/^(?:0|[1-9][0-9]*)$/.test(declared)) {
|
|
150
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'Content-Length must be a non-negative decimal integer.');
|
|
151
|
+
}
|
|
152
|
+
declaredLength = Number(declared);
|
|
153
|
+
if (!Number.isSafeInteger(declaredLength) || declaredLength > maximum) {
|
|
154
|
+
throw new ControlPlaneError('HQ_CONTROL_TOO_LARGE', 'The activation request exceeds its byte limit.');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const chunks = [];
|
|
158
|
+
let total = 0;
|
|
159
|
+
try {
|
|
160
|
+
for await (const chunk of request.body) {
|
|
161
|
+
throwIfAborted(request.signal);
|
|
162
|
+
if (!(chunk instanceof Uint8Array)) {
|
|
163
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request body yielded a non-byte chunk.');
|
|
164
|
+
}
|
|
165
|
+
total += chunk.byteLength;
|
|
166
|
+
if (total > maximum) {
|
|
167
|
+
throw new ControlPlaneError('HQ_CONTROL_TOO_LARGE', 'The activation request exceeds its byte limit.');
|
|
168
|
+
}
|
|
169
|
+
if (declaredLength !== undefined && total > declaredLength) {
|
|
170
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request body does not match Content-Length.');
|
|
171
|
+
}
|
|
172
|
+
chunks.push(Buffer.from(chunk));
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
catch (error) {
|
|
176
|
+
if (error instanceof ControlPlaneError)
|
|
177
|
+
throw error;
|
|
178
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request body could not be read.', { cause: error });
|
|
179
|
+
}
|
|
180
|
+
throwIfAborted(request.signal);
|
|
181
|
+
if (declaredLength !== undefined && total !== declaredLength) {
|
|
182
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request body does not match Content-Length.');
|
|
183
|
+
}
|
|
184
|
+
return Buffer.concat(chunks, total);
|
|
185
|
+
}
|
|
186
|
+
function parseActivationBody(bytes) {
|
|
187
|
+
let input;
|
|
188
|
+
try {
|
|
189
|
+
input = JSON.parse(textDecoder.decode(bytes));
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request must be valid UTF-8 JSON.', { cause: error });
|
|
193
|
+
}
|
|
194
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
195
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request is invalid.');
|
|
196
|
+
}
|
|
197
|
+
const value = input;
|
|
198
|
+
const expected = ['expectedRevision', 'kind', 'releaseIdentity', 'version'];
|
|
199
|
+
if (Object.keys(value).sort().join('\0') !== expected.join('\0')
|
|
200
|
+
|| value.kind !== 'hypequery-deployment-activation-request' || value.version !== 1
|
|
201
|
+
|| typeof value.releaseIdentity !== 'string'
|
|
202
|
+
|| !IDENTITY_PATTERN.test(value.releaseIdentity)
|
|
203
|
+
|| (value.expectedRevision !== null
|
|
204
|
+
&& (typeof value.expectedRevision !== 'string'
|
|
205
|
+
|| !IDENTITY_PATTERN.test(value.expectedRevision)))) {
|
|
206
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The activation request is invalid.');
|
|
207
|
+
}
|
|
208
|
+
return Object.freeze({
|
|
209
|
+
releaseIdentity: value.releaseIdentity,
|
|
210
|
+
expectedRevision: value.expectedRevision,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
function activationError(error) {
|
|
214
|
+
switch (error.code) {
|
|
215
|
+
case 'HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST':
|
|
216
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The deployment activation request is invalid.', { cause: error });
|
|
217
|
+
case 'HQ_DEPLOYMENT_ACTIVATION_TARGET_MISMATCH':
|
|
218
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'The deployment release does not match the requested target.', { cause: error });
|
|
219
|
+
case 'HQ_DEPLOYMENT_ACTIVATION_RELEASE_NOT_FOUND':
|
|
220
|
+
throw new ControlPlaneError('HQ_CONTROL_RELEASE_NOT_FOUND', 'The deployment release was not found.', { cause: error });
|
|
221
|
+
case 'HQ_DEPLOYMENT_ACTIVATION_RELEASE_UNAVAILABLE':
|
|
222
|
+
throw new ControlPlaneError('HQ_CONTROL_RELEASE_UNAVAILABLE', 'The deployment release is temporarily unavailable.', { cause: error });
|
|
223
|
+
default:
|
|
224
|
+
throw new ControlPlaneError('HQ_CONTROL_INTERNAL', 'The deployment activation state could not be processed.', { expose: false, cause: error });
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function activationCall(operation) {
|
|
228
|
+
try {
|
|
229
|
+
return await operation();
|
|
230
|
+
}
|
|
231
|
+
catch (error) {
|
|
232
|
+
if (error instanceof DeploymentActivationError)
|
|
233
|
+
activationError(error);
|
|
234
|
+
throw error;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function activationResponse(status, activation) {
|
|
238
|
+
return jsonResponse(status === 'activated' ? 201 : 200, {
|
|
239
|
+
kind: 'hypequery-deployment-activation-response',
|
|
240
|
+
version: 1,
|
|
241
|
+
status,
|
|
242
|
+
activation,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function validatedHistoryQuery(query, maximum) {
|
|
246
|
+
const values = query ?? {};
|
|
247
|
+
if (Object.keys(values).some(key => key !== 'limit' && key !== 'before')) {
|
|
248
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'History query parameters are invalid.');
|
|
249
|
+
}
|
|
250
|
+
const limitInput = values.limit;
|
|
251
|
+
if (limitInput !== undefined && typeof limitInput !== 'string') {
|
|
252
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'History limit is invalid.');
|
|
253
|
+
}
|
|
254
|
+
const limit = limitInput === undefined ? maximum : Number(limitInput);
|
|
255
|
+
if (!Number.isSafeInteger(limit) || limit < 1 || limit > maximum
|
|
256
|
+
|| (limitInput !== undefined && String(limit) !== limitInput)) {
|
|
257
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'History limit is invalid.');
|
|
258
|
+
}
|
|
259
|
+
const before = values.before;
|
|
260
|
+
if (before !== undefined && typeof before !== 'string') {
|
|
261
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'History cursor is invalid.');
|
|
262
|
+
}
|
|
263
|
+
if (before !== undefined) {
|
|
264
|
+
if (!IDENTITY_PATTERN.test(before)) {
|
|
265
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'History cursor is invalid.');
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
return Object.freeze({ limit, ...(before === undefined ? {} : { before }) });
|
|
269
|
+
}
|
|
270
|
+
export function createDeploymentControlPlane(options) {
|
|
271
|
+
const limits = resolveDeploymentControlPlaneLimits(options.limits);
|
|
272
|
+
async function authenticateAndAuthorize(request, action, target) {
|
|
273
|
+
throwIfAborted(request.signal);
|
|
274
|
+
const token = bearerToken(request.headers);
|
|
275
|
+
const principal = await options.authenticator.authenticate({ token, signal: request.signal });
|
|
276
|
+
if (principal === null) {
|
|
277
|
+
throw new ControlPlaneError('HQ_CONTROL_UNAUTHENTICATED', 'The bearer credential is invalid.', { headers: { 'www-authenticate': 'Bearer' } });
|
|
278
|
+
}
|
|
279
|
+
throwIfAborted(request.signal);
|
|
280
|
+
const allowed = await options.authorizer.authorize({
|
|
281
|
+
principal,
|
|
282
|
+
action,
|
|
283
|
+
target,
|
|
284
|
+
signal: request.signal,
|
|
285
|
+
});
|
|
286
|
+
if (!allowed) {
|
|
287
|
+
throw new ControlPlaneError('HQ_CONTROL_FORBIDDEN', 'The caller is not authorized for the deployment target.');
|
|
288
|
+
}
|
|
289
|
+
throwIfAborted(request.signal);
|
|
290
|
+
}
|
|
291
|
+
async function process(request) {
|
|
292
|
+
const method = request.method.toUpperCase();
|
|
293
|
+
const route = parseRoute(request.path);
|
|
294
|
+
if (!route) {
|
|
295
|
+
throw new ControlPlaneError('HQ_CONTROL_NOT_FOUND', 'Deployment route not found.');
|
|
296
|
+
}
|
|
297
|
+
if (route.kind === 'submission') {
|
|
298
|
+
requireMethod(method, 'POST');
|
|
299
|
+
requireNoQuery(request);
|
|
300
|
+
return options.intake.handle(request);
|
|
301
|
+
}
|
|
302
|
+
const target = route.target;
|
|
303
|
+
if (route.kind === 'activation') {
|
|
304
|
+
if (method === 'GET') {
|
|
305
|
+
requireNoQuery(request);
|
|
306
|
+
requireNoBody(request);
|
|
307
|
+
await authenticateAndAuthorize(request, 'read-current-activation', target);
|
|
308
|
+
const current = await activationCall(() => options.activations.current(target));
|
|
309
|
+
return jsonResponse(200, {
|
|
310
|
+
kind: 'hypequery-deployment-current-activation',
|
|
311
|
+
version: 1,
|
|
312
|
+
target,
|
|
313
|
+
activation: current ?? null,
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
requireMethod(method, 'GET', 'PUT');
|
|
317
|
+
requireNoQuery(request);
|
|
318
|
+
await authenticateAndAuthorize(request, 'activate', target);
|
|
319
|
+
const contentType = requestHeader(request.headers, 'content-type');
|
|
320
|
+
if (!contentType || !JSON_CONTENT_TYPE.test(contentType)) {
|
|
321
|
+
throw new ControlPlaneError('HQ_CONTROL_BAD_REQUEST', 'Activation requests require application/json with UTF-8 encoding.');
|
|
322
|
+
}
|
|
323
|
+
const body = parseActivationBody(await readBoundedBody(request, limits.maxActivationRequestBytes));
|
|
324
|
+
const result = await activationCall(() => options.activations.activate({
|
|
325
|
+
target,
|
|
326
|
+
releaseIdentity: body.releaseIdentity,
|
|
327
|
+
expectedRevision: body.expectedRevision,
|
|
328
|
+
}));
|
|
329
|
+
if (result.status === 'conflict') {
|
|
330
|
+
return jsonResponse(409, {
|
|
331
|
+
kind: 'hypequery-deployment-activation-response',
|
|
332
|
+
version: 1,
|
|
333
|
+
status: 'conflict',
|
|
334
|
+
current: result.current,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
return activationResponse(result.status, result.activation);
|
|
338
|
+
}
|
|
339
|
+
requireMethod(method, 'GET');
|
|
340
|
+
requireNoBody(request);
|
|
341
|
+
await authenticateAndAuthorize(request, 'read-activation-history', target);
|
|
342
|
+
const historyQuery = validatedHistoryQuery(request.query, limits.maxHistoryPageSize);
|
|
343
|
+
const page = await activationCall(() => (options.activations.historyPage(target, historyQuery)));
|
|
344
|
+
return jsonResponse(200, {
|
|
345
|
+
kind: 'hypequery-deployment-activation-history',
|
|
346
|
+
version: 1,
|
|
347
|
+
target,
|
|
348
|
+
activations: page.activations,
|
|
349
|
+
nextBefore: page.nextBefore,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
return Object.freeze({
|
|
353
|
+
async handle(request) {
|
|
354
|
+
try {
|
|
355
|
+
return await process(request);
|
|
356
|
+
}
|
|
357
|
+
catch (error) {
|
|
358
|
+
return errorResponse(error);
|
|
359
|
+
}
|
|
360
|
+
},
|
|
361
|
+
});
|
|
362
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type ProtocolSchemaValueLimits } from '@hypequery/protocol';
|
|
2
|
+
export type DeploymentDataPlaneLimits = ProtocolSchemaValueLimits;
|
|
3
|
+
export declare const DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS: Readonly<ProtocolSchemaValueLimits>;
|
|
4
|
+
export declare function resolveDeploymentDataPlaneLimits(input?: Partial<DeploymentDataPlaneLimits>): Readonly<DeploymentDataPlaneLimits>;
|
|
5
|
+
//# sourceMappingURL=data-plane-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-plane-limits.d.ts","sourceRoot":"","sources":["../src/data-plane-limits.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,yBAAyB,EAC/B,MAAM,qBAAqB,CAAC;AAE7B,MAAM,MAAM,yBAAyB,GAAG,yBAAyB,CAAC;AAElE,eAAO,MAAM,oCAAoC,qCAAuC,CAAC;AAEzF,wBAAgB,gCAAgC,CAC9C,KAAK,GAAE,OAAO,CAAC,yBAAyB,CAAM,GAC7C,QAAQ,CAAC,yBAAyB,CAAC,CAErC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { DEFAULT_PROTOCOL_SCHEMA_VALUE_LIMITS, resolveProtocolSchemaValueLimits, } from '@hypequery/protocol';
|
|
2
|
+
export const DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS = DEFAULT_PROTOCOL_SCHEMA_VALUE_LIMITS;
|
|
3
|
+
export function resolveDeploymentDataPlaneLimits(input = {}) {
|
|
4
|
+
return resolveProtocolSchemaValueLimits({ limits: input });
|
|
5
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
import type { DeploymentRuntimeReferenceExecutionInput } from './data-plane.js';
|
|
3
|
+
import type { DeploymentRuntimeSupervisor } from './runtime-supervisor.js';
|
|
4
|
+
export interface DeploymentRuntimeSupervisorExecutorOptions {
|
|
5
|
+
readonly supervisor: DeploymentRuntimeSupervisor;
|
|
6
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
7
|
+
/** The immutable activation generation used to build the matching data plane. */
|
|
8
|
+
readonly activationRevision: string;
|
|
9
|
+
/** Convert provider-neutral execution context into the runtime handler's argument contract. */
|
|
10
|
+
readonly argument: (input: DeploymentRuntimeReferenceExecutionInput) => unknown;
|
|
11
|
+
}
|
|
12
|
+
export declare function createDeploymentRuntimeSupervisorExecutor(options: DeploymentRuntimeSupervisorExecutorOptions): (input: DeploymentRuntimeReferenceExecutionInput) => Promise<unknown>;
|
|
13
|
+
//# sourceMappingURL=data-plane-runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-plane-runtime.d.ts","sourceRoot":"","sources":["../src/data-plane-runtime.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,+BAA+B,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,KAAK,EACV,wCAAwC,EACzC,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,yBAAyB,CAAC;AAE3E,MAAM,WAAW,0CAA0C;IACzD,QAAQ,CAAC,UAAU,EAAE,2BAA2B,CAAC;IACjD,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,iFAAiF;IACjF,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;IACpC,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,wCAAwC,KAAK,OAAO,CAAC;CACjF;AAED,wBAAgB,yCAAyC,CACvD,OAAO,EAAE,0CAA0C,GAClD,CAAC,KAAK,EAAE,wCAAwC,KAAK,OAAO,CAAC,OAAO,CAAC,CAWvE"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export function createDeploymentRuntimeSupervisorExecutor(options) {
|
|
2
|
+
if (!/^[0-9a-f]{64}$/.test(options.activationRevision)) {
|
|
3
|
+
throw new RangeError('activationRevision must be a lowercase SHA-256 identity');
|
|
4
|
+
}
|
|
5
|
+
return async (input) => await options.supervisor.invoke({
|
|
6
|
+
target: options.target,
|
|
7
|
+
activationRevision: options.activationRevision,
|
|
8
|
+
query: input.query.name,
|
|
9
|
+
argument: options.argument(input),
|
|
10
|
+
signal: input.signal,
|
|
11
|
+
});
|
|
12
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { ProtocolDeploymentContract, ProtocolNamedQueryContract, ProtocolQueryImplementation } from '@hypequery/protocol';
|
|
2
|
+
import { type DeploymentDataPlaneLimits } from './data-plane-limits.js';
|
|
3
|
+
export type DeploymentDataPlaneErrorCode = 'HQ_DATA_PLANE_CONFIGURATION' | 'HQ_DATA_PLANE_ROUTE_NOT_FOUND' | 'HQ_DATA_PLANE_METHOD_NOT_ALLOWED' | 'HQ_DATA_PLANE_UNAUTHENTICATED' | 'HQ_DATA_PLANE_FORBIDDEN' | 'HQ_DATA_PLANE_TENANT_REQUIRED' | 'HQ_DATA_PLANE_INPUT_INVALID' | 'HQ_DATA_PLANE_OUTPUT_INVALID' | 'HQ_DATA_PLANE_EXECUTOR_UNAVAILABLE' | 'HQ_DATA_PLANE_EXECUTION_FAILED' | 'HQ_DATA_PLANE_ABORTED';
|
|
4
|
+
export declare class DeploymentDataPlaneError extends Error {
|
|
5
|
+
readonly code: DeploymentDataPlaneErrorCode;
|
|
6
|
+
readonly path?: string;
|
|
7
|
+
constructor(code: DeploymentDataPlaneErrorCode, message: string, options?: {
|
|
8
|
+
readonly path?: string;
|
|
9
|
+
readonly cause?: unknown;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
export interface DeploymentDataPlanePrincipal {
|
|
13
|
+
readonly subject?: string;
|
|
14
|
+
readonly roles?: readonly string[];
|
|
15
|
+
readonly scopes?: readonly string[];
|
|
16
|
+
readonly claims?: Readonly<Record<string, unknown>>;
|
|
17
|
+
}
|
|
18
|
+
export interface DeploymentDataPlaneRequest {
|
|
19
|
+
readonly method: string;
|
|
20
|
+
readonly path: string;
|
|
21
|
+
readonly input?: unknown;
|
|
22
|
+
readonly credentials?: unknown;
|
|
23
|
+
readonly signal?: AbortSignal;
|
|
24
|
+
}
|
|
25
|
+
export interface DeploymentDataPlaneAuthenticationInput {
|
|
26
|
+
readonly credentials: unknown;
|
|
27
|
+
readonly request: DeploymentDataPlaneRequest;
|
|
28
|
+
readonly query: ProtocolNamedQueryContract;
|
|
29
|
+
}
|
|
30
|
+
export interface DeploymentDataPlaneTenantInput {
|
|
31
|
+
readonly principal: DeploymentDataPlanePrincipal | null;
|
|
32
|
+
readonly request: DeploymentDataPlaneRequest;
|
|
33
|
+
readonly query: ProtocolNamedQueryContract;
|
|
34
|
+
}
|
|
35
|
+
export interface DeploymentDataPlaneExecutionInput {
|
|
36
|
+
readonly query: ProtocolNamedQueryContract;
|
|
37
|
+
readonly input: unknown;
|
|
38
|
+
readonly principal: DeploymentDataPlanePrincipal | null;
|
|
39
|
+
readonly tenant: unknown;
|
|
40
|
+
readonly request: DeploymentDataPlaneRequest;
|
|
41
|
+
readonly signal?: AbortSignal;
|
|
42
|
+
}
|
|
43
|
+
export interface DeploymentSemanticPlanExecutionInput extends DeploymentDataPlaneExecutionInput {
|
|
44
|
+
readonly implementation: Extract<ProtocolQueryImplementation, {
|
|
45
|
+
readonly kind: 'semantic-plan';
|
|
46
|
+
}>;
|
|
47
|
+
readonly deployment: ProtocolDeploymentContract;
|
|
48
|
+
}
|
|
49
|
+
export interface DeploymentCompiledSqlExecutionInput extends DeploymentDataPlaneExecutionInput {
|
|
50
|
+
readonly implementation: Extract<ProtocolQueryImplementation, {
|
|
51
|
+
readonly kind: 'compiled-sql';
|
|
52
|
+
}>;
|
|
53
|
+
readonly parameters: Readonly<Record<string, unknown>>;
|
|
54
|
+
}
|
|
55
|
+
export interface DeploymentRuntimeReferenceExecutionInput extends DeploymentDataPlaneExecutionInput {
|
|
56
|
+
readonly implementation: Extract<ProtocolQueryImplementation, {
|
|
57
|
+
readonly kind: 'runtime-reference';
|
|
58
|
+
}>;
|
|
59
|
+
}
|
|
60
|
+
export interface DeploymentDataPlaneResult {
|
|
61
|
+
readonly query: string;
|
|
62
|
+
readonly output: unknown;
|
|
63
|
+
readonly cacheTtlMs?: number;
|
|
64
|
+
}
|
|
65
|
+
export interface DeploymentDataPlane {
|
|
66
|
+
execute(request: DeploymentDataPlaneRequest): Promise<DeploymentDataPlaneResult>;
|
|
67
|
+
}
|
|
68
|
+
export interface DeploymentDataPlaneOptions {
|
|
69
|
+
readonly deployment: ProtocolDeploymentContract;
|
|
70
|
+
readonly authenticate?: (input: DeploymentDataPlaneAuthenticationInput) => Promise<DeploymentDataPlanePrincipal | null>;
|
|
71
|
+
readonly resolveTenant?: (input: DeploymentDataPlaneTenantInput) => Promise<unknown>;
|
|
72
|
+
readonly executeSemanticPlan?: (input: DeploymentSemanticPlanExecutionInput) => Promise<unknown>;
|
|
73
|
+
readonly executeCompiledSql?: (input: DeploymentCompiledSqlExecutionInput) => Promise<unknown>;
|
|
74
|
+
readonly executeRuntimeReference?: (input: DeploymentRuntimeReferenceExecutionInput) => Promise<unknown>;
|
|
75
|
+
readonly limits?: Partial<DeploymentDataPlaneLimits>;
|
|
76
|
+
}
|
|
77
|
+
export declare function createDeploymentDataPlane(options: DeploymentDataPlaneOptions): DeploymentDataPlane;
|
|
78
|
+
//# sourceMappingURL=data-plane.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"data-plane.d.ts","sourceRoot":"","sources":["../src/data-plane.ts"],"names":[],"mappings":"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"}
|