@hypequery/deployment 0.2.0 → 0.3.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 +95 -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/index.d.ts +14 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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 +70 -0
- package/dist/runtime-supervisor.d.ts.map +1 -0
- package/dist/runtime-supervisor.js +254 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# @hypequery/deployment
|
|
2
2
|
|
|
3
|
-
Provider-neutral verification
|
|
4
|
-
bundles.
|
|
3
|
+
Provider-neutral verification, intake, activation, and HTTP control-plane
|
|
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
|
|
@@ -152,4 +152,97 @@ append-only chain. It has no mutable pointer file or persistent lock to become
|
|
|
152
152
|
stale after a crash. Activation does not load runtime code, route traffic,
|
|
153
153
|
perform health checks, or authorize callers; those remain provider concerns.
|
|
154
154
|
|
|
155
|
+
## HTTP control plane
|
|
156
|
+
|
|
157
|
+
`createDeploymentControlPlane` combines intake and activation behind closed v1
|
|
158
|
+
routes. Activation reads and writes use a separate target-scoped authorizer;
|
|
159
|
+
write authorization completes before the small JSON request body is consumed.
|
|
160
|
+
The Fetch and Node adapters preserve streaming multipart submission bodies.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
import {
|
|
164
|
+
createDeploymentControlPlane,
|
|
165
|
+
createDeploymentControlPlaneNodeHandler,
|
|
166
|
+
} from '@hypequery/deployment';
|
|
167
|
+
|
|
168
|
+
const controlPlane = createDeploymentControlPlane({
|
|
169
|
+
intake,
|
|
170
|
+
activations,
|
|
171
|
+
authenticator,
|
|
172
|
+
authorizer: {
|
|
173
|
+
async authorize({ principal, action, target }) {
|
|
174
|
+
return canControlDeployment(principal, action, target);
|
|
175
|
+
},
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
const nodeHandler = createDeploymentControlPlaneNodeHandler(controlPlane);
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
The control plane exposes release submission, compare-and-swap activation,
|
|
183
|
+
current state, and bounded cursor history. It returns stable, bounded JSON error
|
|
184
|
+
codes and suppresses internal provider and filesystem details. The HTTP
|
|
185
|
+
contract is specified in `specs/deployment/0003-control-plane-http.md`.
|
|
186
|
+
|
|
187
|
+
## Runtime materialization
|
|
188
|
+
|
|
189
|
+
`createDeploymentRuntimeMaterializer` converts the current target activation
|
|
190
|
+
into a private runtime snapshot. It revalidates the accepted release and closed
|
|
191
|
+
bundle, copies and hashes each runtime artifact without following symbolic
|
|
192
|
+
links, and confirms the activation revision again before returning.
|
|
193
|
+
|
|
194
|
+
```ts
|
|
195
|
+
import { createDeploymentRuntimeMaterializer } from '@hypequery/deployment';
|
|
196
|
+
|
|
197
|
+
const materializer = createDeploymentRuntimeMaterializer({
|
|
198
|
+
activations,
|
|
199
|
+
releases: store,
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const snapshot = await materializer.current({
|
|
203
|
+
project: 'analytics',
|
|
204
|
+
environment: 'production',
|
|
205
|
+
});
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Artifact `read()` methods return fresh byte copies, so neither callers nor later
|
|
209
|
+
changes to durable storage can alter a materialized snapshot. Runtime imports,
|
|
210
|
+
process lifecycle, readiness, and traffic switching remain separate concerns.
|
|
211
|
+
|
|
212
|
+
## Runtime supervision
|
|
213
|
+
|
|
214
|
+
`createDeploymentRuntimeSupervisor` starts materialized snapshots through a
|
|
215
|
+
runtime factory, checks candidate readiness, confirms activation again, and
|
|
216
|
+
atomically changes the generation used for new named-query invocations. Failed
|
|
217
|
+
or superseded candidates never displace a healthy generation.
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import {
|
|
221
|
+
createDeploymentRuntimeSupervisor,
|
|
222
|
+
createNodeWorkerDeploymentRuntimeFactory,
|
|
223
|
+
} from '@hypequery/deployment';
|
|
224
|
+
|
|
225
|
+
const supervisor = createDeploymentRuntimeSupervisor({
|
|
226
|
+
materializer,
|
|
227
|
+
factory: createNodeWorkerDeploymentRuntimeFactory(),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await supervisor.reconcile({ project: 'analytics', environment: 'production' });
|
|
231
|
+
const result = await supervisor.invoke({
|
|
232
|
+
target: { project: 'analytics', environment: 'production' },
|
|
233
|
+
query: 'orders',
|
|
234
|
+
argument: { input, ctx: { tenantId } },
|
|
235
|
+
});
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Calls already assigned to an old generation may finish after cutover. That
|
|
239
|
+
generation receives no new calls and closes when its in-flight work reaches
|
|
240
|
+
zero or the drain deadline expires. The reference Node factory loads exact
|
|
241
|
+
materialized bytes in worker threads and removes their temporary files on
|
|
242
|
+
shutdown. Provider factories can implement Python, process, container, or
|
|
243
|
+
remote-sandbox isolation behind the same lifecycle interface.
|
|
244
|
+
|
|
245
|
+
The reference worker is a lifecycle boundary, not a hostile-code security
|
|
246
|
+
sandbox. Only trusted deployment code should use it directly.
|
|
247
|
+
|
|
155
248
|
The package is ESM-only and requires Node.js 20 or newer.
|
package/dist/activation.d.ts
CHANGED
|
@@ -29,6 +29,14 @@ export type DeploymentActivationResult = {
|
|
|
29
29
|
readonly status: 'conflict';
|
|
30
30
|
readonly current: DeploymentActivationRecord | null;
|
|
31
31
|
};
|
|
32
|
+
export interface DeploymentActivationHistoryQuery {
|
|
33
|
+
readonly limit: number;
|
|
34
|
+
readonly before?: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DeploymentActivationHistoryPage {
|
|
37
|
+
readonly activations: readonly DeploymentActivationRecord[];
|
|
38
|
+
readonly nextBefore: string | null;
|
|
39
|
+
}
|
|
32
40
|
export interface DeploymentActivationRelease {
|
|
33
41
|
readonly release: ProtocolDeploymentReleaseEnvelope;
|
|
34
42
|
readonly releaseIdentity: string;
|
|
@@ -46,6 +54,9 @@ export interface DeploymentActivationRegistry {
|
|
|
46
54
|
activate(request: DeploymentActivationRequest): Promise<DeploymentActivationResult>;
|
|
47
55
|
current(target: ProtocolDeploymentReleaseTarget): Promise<DeploymentActivationRecord | undefined>;
|
|
48
56
|
history(target: ProtocolDeploymentReleaseTarget): Promise<readonly DeploymentActivationRecord[]>;
|
|
57
|
+
historyPage(target: ProtocolDeploymentReleaseTarget, query: DeploymentActivationHistoryQuery): Promise<DeploymentActivationHistoryPage>;
|
|
49
58
|
}
|
|
59
|
+
/** Validate and recompute an immutable deployment activation record. */
|
|
60
|
+
export declare function validateDeploymentActivationRecord(input: unknown): DeploymentActivationRecord;
|
|
50
61
|
export declare function createFileSystemDeploymentActivationRegistry(options: FileSystemDeploymentActivationRegistryOptions): DeploymentActivationRegistry;
|
|
51
62
|
//# sourceMappingURL=activation.d.ts.map
|
package/dist/activation.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../src/activation.ts"],"names":[],"mappings":"AAYA,OAAO,EAGL,KAAK,iCAAiC,EACtC,KAAK,+BAA+B,EACrC,MAAM,qBAAqB,CAAC;AAe7B,MAAM,MAAM,6BAA6B,GACrC,wCAAwC,GACxC,0CAA0C,GAC1C,4CAA4C,GAC5C,8CAA8C,GAC9C,0CAA0C,GAC1C,wCAAwC,GACxC,6BAA6B,CAAC;AAElC,qBAAa,yBAA0B,SAAQ,KAAK;IAClD,QAAQ,CAAC,IAAI,EAAE,6BAA6B,CAAC;gBAG3C,IAAI,EAAE,6BAA6B,EACnC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,IAAI,EAAE,iCAAiC,CAAC;IACjD,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,uBAAuB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;CAC9B;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,6EAA6E;IAC7E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C;AAED,MAAM,MAAM,0BAA0B,GAClC;IACA,QAAQ,CAAC,MAAM,EAAE,WAAW,GAAG,gBAAgB,CAAC;IAChD,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;CACjD,GACC;IACA,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;IAC5B,QAAQ,CAAC,OAAO,EAAE,0BAA0B,GAAG,IAAI,CAAC;CACrD,CAAC;AAEJ,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAED,MAAM,WAAW,uBAAuB;IACtC,6FAA6F;IAC7F,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,2BAA2B,GAAG,SAAS,CAAC,CAAC;CACjF;AAED,MAAM,WAAW,6CAA6C;IAC5D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;IAC3C,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAED,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,OAAO,EAAE,2BAA2B,GAAG,OAAO,CAAC,0BAA0B,CAAC,CAAC;IACpF,OAAO,CACL,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,0BAA0B,GAAG,SAAS,CAAC,CAAC;IACnD,OAAO,CACL,MAAM,EAAE,+BAA+B,GACtC,OAAO,CAAC,SAAS,0BAA0B,EAAE,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../src/activation.ts"],"names":[],"mappings":"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"}
|
package/dist/activation.js
CHANGED
|
@@ -128,21 +128,16 @@ function requireRecord(input) {
|
|
|
128
128
|
}
|
|
129
129
|
return value;
|
|
130
130
|
}
|
|
131
|
-
function
|
|
131
|
+
function prepareValidatedActivation(input) {
|
|
132
132
|
const value = requireRecord(input);
|
|
133
133
|
if (value.kind !== 'hypequery-deployment-activation' || value.version !== 1) {
|
|
134
134
|
throw new Error('Activation record kind or version is invalid.');
|
|
135
135
|
}
|
|
136
136
|
const target = validateTarget(value.target, 'HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE');
|
|
137
|
-
if (!targetsEqual(target, expectedTarget)) {
|
|
138
|
-
throw new Error('Activation record target is inconsistent.');
|
|
139
|
-
}
|
|
140
137
|
const releaseIdentity = requireIdentity(value.releaseIdentity, 'Stored release identity', 'HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE');
|
|
141
138
|
const previousRevision = requireNullableIdentity(value.previousRevision, 'Stored previous revision', 'HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE');
|
|
142
139
|
const previousReleaseIdentity = requireNullableIdentity(value.previousReleaseIdentity, 'Stored previous release identity', 'HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE');
|
|
143
|
-
if (previousRevision !==
|
|
144
|
-
|| previousReleaseIdentity !== expectedPreviousReleaseIdentity
|
|
145
|
-
|| (previousRevision === null) !== (previousReleaseIdentity === null)) {
|
|
140
|
+
if ((previousRevision === null) !== (previousReleaseIdentity === null)) {
|
|
146
141
|
throw new Error('Activation record predecessor is inconsistent.');
|
|
147
142
|
}
|
|
148
143
|
if (typeof value.activatedAt !== 'string') {
|
|
@@ -164,6 +159,21 @@ function validateStoredActivation(input, expectedTarget, expectedPreviousRevisio
|
|
|
164
159
|
}
|
|
165
160
|
return prepared;
|
|
166
161
|
}
|
|
162
|
+
/** Validate and recompute an immutable deployment activation record. */
|
|
163
|
+
export function validateDeploymentActivationRecord(input) {
|
|
164
|
+
return prepareValidatedActivation(input).record;
|
|
165
|
+
}
|
|
166
|
+
function validateStoredActivation(input, expectedTarget, expectedPreviousRevision, expectedPreviousReleaseIdentity) {
|
|
167
|
+
const prepared = prepareValidatedActivation(input);
|
|
168
|
+
if (!targetsEqual(prepared.record.target, expectedTarget)) {
|
|
169
|
+
throw new Error('Activation record target is inconsistent.');
|
|
170
|
+
}
|
|
171
|
+
if (prepared.record.previousRevision !== expectedPreviousRevision
|
|
172
|
+
|| prepared.record.previousReleaseIdentity !== expectedPreviousReleaseIdentity) {
|
|
173
|
+
throw new Error('Activation record predecessor is inconsistent.');
|
|
174
|
+
}
|
|
175
|
+
return prepared;
|
|
176
|
+
}
|
|
167
177
|
async function pathExists(filePath) {
|
|
168
178
|
try {
|
|
169
179
|
await lstat(filePath);
|
|
@@ -335,7 +345,7 @@ async function readStoredActivation(claimDirectory, target, previousRevision, pr
|
|
|
335
345
|
}
|
|
336
346
|
return prepared;
|
|
337
347
|
}
|
|
338
|
-
async function
|
|
348
|
+
async function visitHistory(targetDirectory, target, visitor) {
|
|
339
349
|
try {
|
|
340
350
|
await readCanonicalTarget(targetDirectory, target);
|
|
341
351
|
const claimsDirectory = path.join(targetDirectory, CLAIMS_DIRECTORY);
|
|
@@ -347,7 +357,6 @@ async function resolveHistory(targetDirectory, target) {
|
|
|
347
357
|
}
|
|
348
358
|
const remaining = new Set(claimNames);
|
|
349
359
|
const revisions = new Set();
|
|
350
|
-
const history = [];
|
|
351
360
|
let previousRevision = null;
|
|
352
361
|
let previousReleaseIdentity = null;
|
|
353
362
|
// Following predecessor-named claims derives the head without trusting a
|
|
@@ -361,14 +370,13 @@ async function resolveHistory(targetDirectory, target) {
|
|
|
361
370
|
throw new Error('Deployment activation history contains a cycle.');
|
|
362
371
|
}
|
|
363
372
|
revisions.add(prepared.record.revision);
|
|
364
|
-
|
|
373
|
+
visitor(prepared.record);
|
|
365
374
|
previousRevision = prepared.record.revision;
|
|
366
375
|
previousReleaseIdentity = prepared.record.releaseIdentity;
|
|
367
376
|
}
|
|
368
377
|
if (remaining.size > 0) {
|
|
369
378
|
throw new Error('Deployment activation history contains unreachable claims.');
|
|
370
379
|
}
|
|
371
|
-
return Object.freeze(history);
|
|
372
380
|
}
|
|
373
381
|
catch (error) {
|
|
374
382
|
if (error instanceof DeploymentActivationError)
|
|
@@ -378,8 +386,53 @@ async function resolveHistory(targetDirectory, target) {
|
|
|
378
386
|
throw activationError('HQ_DEPLOYMENT_ACTIVATION_CORRUPT_STATE', 'Stored deployment activation history is invalid.', error);
|
|
379
387
|
}
|
|
380
388
|
}
|
|
381
|
-
function
|
|
382
|
-
|
|
389
|
+
async function resolveHistory(targetDirectory, target) {
|
|
390
|
+
const history = [];
|
|
391
|
+
await visitHistory(targetDirectory, target, record => { history.push(record); });
|
|
392
|
+
return Object.freeze(history);
|
|
393
|
+
}
|
|
394
|
+
async function resolveCurrent(targetDirectory, target) {
|
|
395
|
+
let current;
|
|
396
|
+
await visitHistory(targetDirectory, target, record => { current = record; });
|
|
397
|
+
return current;
|
|
398
|
+
}
|
|
399
|
+
function validateHistoryQuery(input) {
|
|
400
|
+
if (!Number.isSafeInteger(input?.limit) || input.limit < 1
|
|
401
|
+
|| (input.before !== undefined && !IDENTITY_PATTERN.test(input.before))) {
|
|
402
|
+
throw activationError('HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST', 'Deployment activation history query is invalid.');
|
|
403
|
+
}
|
|
404
|
+
return Object.freeze({
|
|
405
|
+
limit: input.limit,
|
|
406
|
+
...(input.before === undefined ? {} : { before: input.before }),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
async function resolveHistoryPage(targetDirectory, target, query) {
|
|
410
|
+
const retained = [];
|
|
411
|
+
let beforeFound = query.before === undefined;
|
|
412
|
+
let beforeReached = false;
|
|
413
|
+
await visitHistory(targetDirectory, target, record => {
|
|
414
|
+
if (record.revision === query.before) {
|
|
415
|
+
beforeFound = true;
|
|
416
|
+
beforeReached = true;
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
if (beforeReached)
|
|
420
|
+
return;
|
|
421
|
+
retained.push(record);
|
|
422
|
+
if (retained.length > query.limit + 1)
|
|
423
|
+
retained.shift();
|
|
424
|
+
});
|
|
425
|
+
if (!beforeFound) {
|
|
426
|
+
throw activationError('HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST', 'Deployment activation history cursor was not found.');
|
|
427
|
+
}
|
|
428
|
+
const hasOlder = retained.length > query.limit;
|
|
429
|
+
if (hasOlder)
|
|
430
|
+
retained.shift();
|
|
431
|
+
const activations = Object.freeze(retained);
|
|
432
|
+
return Object.freeze({
|
|
433
|
+
activations,
|
|
434
|
+
nextBefore: hasOlder ? activations[0].revision : null,
|
|
435
|
+
});
|
|
383
436
|
}
|
|
384
437
|
export function createFileSystemDeploymentActivationRegistry(options) {
|
|
385
438
|
if (typeof options.directory !== 'string' || options.directory.length < 1) {
|
|
@@ -433,8 +486,37 @@ export function createFileSystemDeploymentActivationRegistry(options) {
|
|
|
433
486
|
throw activationError('HQ_DEPLOYMENT_ACTIVATION_IO', 'Could not read deployment activation history.', error);
|
|
434
487
|
}
|
|
435
488
|
}
|
|
436
|
-
async function current(
|
|
437
|
-
|
|
489
|
+
async function current(targetInput) {
|
|
490
|
+
const target = validateTarget(targetInput, 'HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST');
|
|
491
|
+
try {
|
|
492
|
+
const key = sha256(TARGET_IDENTITY_DOMAIN, targetCanonical(target));
|
|
493
|
+
const targetDirectory = path.join(await activationRoot(), key);
|
|
494
|
+
if (!await pathExists(targetDirectory))
|
|
495
|
+
return undefined;
|
|
496
|
+
return await resolveCurrent(targetDirectory, target);
|
|
497
|
+
}
|
|
498
|
+
catch (error) {
|
|
499
|
+
if (error instanceof DeploymentActivationError)
|
|
500
|
+
throw error;
|
|
501
|
+
throw activationError('HQ_DEPLOYMENT_ACTIVATION_IO', 'Could not read the current deployment activation.', error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async function historyPage(targetInput, queryInput) {
|
|
505
|
+
const target = validateTarget(targetInput, 'HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST');
|
|
506
|
+
const query = validateHistoryQuery(queryInput);
|
|
507
|
+
try {
|
|
508
|
+
const key = sha256(TARGET_IDENTITY_DOMAIN, targetCanonical(target));
|
|
509
|
+
const targetDirectory = path.join(await activationRoot(), key);
|
|
510
|
+
if (!await pathExists(targetDirectory)) {
|
|
511
|
+
return Object.freeze({ activations: Object.freeze([]), nextBefore: null });
|
|
512
|
+
}
|
|
513
|
+
return await resolveHistoryPage(targetDirectory, target, query);
|
|
514
|
+
}
|
|
515
|
+
catch (error) {
|
|
516
|
+
if (error instanceof DeploymentActivationError)
|
|
517
|
+
throw error;
|
|
518
|
+
throw activationError('HQ_DEPLOYMENT_ACTIVATION_IO', 'Could not read deployment activation history.', error);
|
|
519
|
+
}
|
|
438
520
|
}
|
|
439
521
|
async function activate(request) {
|
|
440
522
|
const target = validateTarget(request.target, 'HQ_DEPLOYMENT_ACTIVATION_INVALID_REQUEST');
|
|
@@ -467,8 +549,7 @@ export function createFileSystemDeploymentActivationRegistry(options) {
|
|
|
467
549
|
try {
|
|
468
550
|
const activations = await activationRoot();
|
|
469
551
|
const targetDirectory = await ensureTargetDirectory(activations, target);
|
|
470
|
-
const
|
|
471
|
-
const existing = currentActivation(existingHistory);
|
|
552
|
+
const existing = await resolveCurrent(targetDirectory, target);
|
|
472
553
|
if (existing?.releaseIdentity === releaseIdentity) {
|
|
473
554
|
return Object.freeze({ status: 'already-active', activation: existing });
|
|
474
555
|
}
|
|
@@ -514,7 +595,7 @@ export function createFileSystemDeploymentActivationRegistry(options) {
|
|
|
514
595
|
if (published) {
|
|
515
596
|
return Object.freeze({ status: 'activated', activation: prepared.record });
|
|
516
597
|
}
|
|
517
|
-
const resolved =
|
|
598
|
+
const resolved = await resolveCurrent(targetDirectory, target);
|
|
518
599
|
if (resolved?.releaseIdentity === releaseIdentity) {
|
|
519
600
|
return Object.freeze({ status: 'already-active', activation: resolved });
|
|
520
601
|
}
|
|
@@ -526,5 +607,5 @@ export function createFileSystemDeploymentActivationRegistry(options) {
|
|
|
526
607
|
throw activationError('HQ_DEPLOYMENT_ACTIVATION_IO', 'Could not update deployment activation state.', error);
|
|
527
608
|
}
|
|
528
609
|
}
|
|
529
|
-
return Object.freeze({ activate, current, history });
|
|
610
|
+
return Object.freeze({ activate, current, history, historyPage });
|
|
530
611
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import type { DeploymentControlPlane } from './control-plane.js';
|
|
3
|
+
export type DeploymentControlPlaneFetchHandler = (request: Request) => Promise<Response>;
|
|
4
|
+
export type DeploymentControlPlaneNodeHandler = (request: IncomingMessage, response: ServerResponse) => Promise<void>;
|
|
5
|
+
export declare function createDeploymentControlPlaneFetchHandler(controlPlane: DeploymentControlPlane): DeploymentControlPlaneFetchHandler;
|
|
6
|
+
export declare function createDeploymentControlPlaneNodeHandler(controlPlane: DeploymentControlPlane): DeploymentControlPlaneNodeHandler;
|
|
7
|
+
//# sourceMappingURL=control-plane-adapters.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-plane-adapters.d.ts","sourceRoot":"","sources":["../src/control-plane-adapters.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,WAAW,CAAC;AACjE,OAAO,KAAK,EACV,sBAAsB,EAGvB,MAAM,oBAAoB,CAAC;AAE5B,MAAM,MAAM,kCAAkC,GAAG,CAAC,OAAO,EAAE,OAAO,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAC;AACzF,MAAM,MAAM,iCAAiC,GAAG,CAC9C,OAAO,EAAE,eAAe,EACxB,QAAQ,EAAE,cAAc,KACrB,OAAO,CAAC,IAAI,CAAC,CAAC;AAkHnB,wBAAgB,wCAAwC,CACtD,YAAY,EAAE,sBAAsB,GACnC,kCAAkC,CAkBpC;AAED,wBAAgB,uCAAuC,CACrD,YAAY,EAAE,sBAAsB,GACnC,iCAAiC,CAgCnC"}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const INTERNAL_BODY = '{"error":{"code":"HQ_CONTROL_INTERNAL","message":"The deployment control-plane request could not be processed."}}\n';
|
|
2
|
+
const INTERNAL_RESPONSE = Object.freeze({
|
|
3
|
+
status: 500,
|
|
4
|
+
headers: Object.freeze({
|
|
5
|
+
'content-type': 'application/json; charset=utf-8',
|
|
6
|
+
'content-length': String(Buffer.byteLength(INTERNAL_BODY)),
|
|
7
|
+
'cache-control': 'no-store',
|
|
8
|
+
}),
|
|
9
|
+
body: INTERNAL_BODY,
|
|
10
|
+
});
|
|
11
|
+
const FETCH_SINGLETON_HEADERS = new Set([
|
|
12
|
+
'authorization',
|
|
13
|
+
'content-length',
|
|
14
|
+
'content-type',
|
|
15
|
+
'idempotency-key',
|
|
16
|
+
'x-hypequery-bundle-identity',
|
|
17
|
+
'x-hypequery-release-identity',
|
|
18
|
+
]);
|
|
19
|
+
function queryParameters(search) {
|
|
20
|
+
const query = Object.create(null);
|
|
21
|
+
for (const [name, value] of search) {
|
|
22
|
+
const current = query[name];
|
|
23
|
+
if (current === undefined) {
|
|
24
|
+
query[name] = value;
|
|
25
|
+
}
|
|
26
|
+
else if (typeof current === 'string') {
|
|
27
|
+
query[name] = Object.freeze([current, value]);
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
query[name] = Object.freeze([...current, value]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return Object.freeze(query);
|
|
34
|
+
}
|
|
35
|
+
function fetchHeaders(input) {
|
|
36
|
+
const headers = Object.create(null);
|
|
37
|
+
for (const [name, value] of input.entries()) {
|
|
38
|
+
headers[name] = value;
|
|
39
|
+
if (FETCH_SINGLETON_HEADERS.has(name) && value.includes(',')) {
|
|
40
|
+
// Fetch combines duplicate header lines before exposing Headers. Reify a
|
|
41
|
+
// second case-insensitive entry so the core singleton guard rejects it.
|
|
42
|
+
headers[name.toUpperCase()] = value;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return Object.freeze(headers);
|
|
46
|
+
}
|
|
47
|
+
async function* fetchBody(input) {
|
|
48
|
+
if (input === null)
|
|
49
|
+
return;
|
|
50
|
+
const reader = input.getReader();
|
|
51
|
+
try {
|
|
52
|
+
while (true) {
|
|
53
|
+
const result = await reader.read();
|
|
54
|
+
if (result.done)
|
|
55
|
+
return;
|
|
56
|
+
yield result.value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
finally {
|
|
60
|
+
reader.releaseLock();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
function nodeHeaders(request) {
|
|
64
|
+
const headers = Object.create(null);
|
|
65
|
+
for (let index = 0; index < request.rawHeaders.length; index += 2) {
|
|
66
|
+
const name = request.rawHeaders[index].toLowerCase();
|
|
67
|
+
const value = request.rawHeaders[index + 1];
|
|
68
|
+
if (headers[name] === undefined) {
|
|
69
|
+
headers[name] = value;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
// Keep a second case-insensitive entry so the core duplicate-header guard
|
|
73
|
+
// observes duplicates instead of accepting Node's comma-joined value.
|
|
74
|
+
headers[name.toUpperCase()] = value;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return Object.freeze(headers);
|
|
78
|
+
}
|
|
79
|
+
async function* nodeBody(request) {
|
|
80
|
+
for await (const chunk of request) {
|
|
81
|
+
yield chunk instanceof Uint8Array ? chunk : Buffer.from(chunk);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function nodeHasBody(headers) {
|
|
85
|
+
return Object.entries(headers).some(([name, value]) => {
|
|
86
|
+
const normalized = name.toLowerCase();
|
|
87
|
+
return normalized === 'transfer-encoding'
|
|
88
|
+
|| (normalized === 'content-length' && value !== '0');
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
function internalFetchResponse() {
|
|
92
|
+
return new Response(INTERNAL_RESPONSE.body, {
|
|
93
|
+
status: INTERNAL_RESPONSE.status,
|
|
94
|
+
headers: INTERNAL_RESPONSE.headers,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
function sendNodeResponse(response, controlResponse) {
|
|
98
|
+
if (response.writableEnded || response.headersSent)
|
|
99
|
+
return;
|
|
100
|
+
response.statusCode = controlResponse.status;
|
|
101
|
+
for (const [name, value] of Object.entries(controlResponse.headers)) {
|
|
102
|
+
response.setHeader(name, value);
|
|
103
|
+
}
|
|
104
|
+
response.end(controlResponse.body);
|
|
105
|
+
}
|
|
106
|
+
export function createDeploymentControlPlaneFetchHandler(controlPlane) {
|
|
107
|
+
return async (request) => {
|
|
108
|
+
try {
|
|
109
|
+
const url = new URL(request.url);
|
|
110
|
+
const response = await controlPlane.handle({
|
|
111
|
+
method: request.method,
|
|
112
|
+
path: url.pathname,
|
|
113
|
+
query: queryParameters(url.searchParams),
|
|
114
|
+
headers: fetchHeaders(request.headers),
|
|
115
|
+
body: fetchBody(request.body),
|
|
116
|
+
hasBody: request.body !== null,
|
|
117
|
+
signal: request.signal,
|
|
118
|
+
});
|
|
119
|
+
return new Response(response.body, { status: response.status, headers: response.headers });
|
|
120
|
+
}
|
|
121
|
+
catch {
|
|
122
|
+
return internalFetchResponse();
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
export function createDeploymentControlPlaneNodeHandler(controlPlane) {
|
|
127
|
+
return async (request, response) => {
|
|
128
|
+
const abort = new AbortController();
|
|
129
|
+
const abortRequest = () => {
|
|
130
|
+
if (!request.complete)
|
|
131
|
+
abort.abort(new Error('The request connection was closed.'));
|
|
132
|
+
};
|
|
133
|
+
const abortResponse = () => {
|
|
134
|
+
if (!response.writableEnded)
|
|
135
|
+
abort.abort(new Error('The response connection was closed.'));
|
|
136
|
+
};
|
|
137
|
+
request.socket.once('close', abortRequest);
|
|
138
|
+
response.once('close', abortResponse);
|
|
139
|
+
if (request.destroyed && !request.complete)
|
|
140
|
+
abortRequest();
|
|
141
|
+
try {
|
|
142
|
+
const url = new URL(request.url ?? '/', 'http://deployment-control-plane.invalid');
|
|
143
|
+
const headers = nodeHeaders(request);
|
|
144
|
+
const controlResponse = await controlPlane.handle({
|
|
145
|
+
method: request.method ?? 'GET',
|
|
146
|
+
path: url.pathname,
|
|
147
|
+
query: queryParameters(url.searchParams),
|
|
148
|
+
headers,
|
|
149
|
+
body: nodeBody(request),
|
|
150
|
+
hasBody: nodeHasBody(headers),
|
|
151
|
+
signal: abort.signal,
|
|
152
|
+
});
|
|
153
|
+
sendNodeResponse(response, controlResponse);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
sendNodeResponse(response, INTERNAL_RESPONSE);
|
|
157
|
+
}
|
|
158
|
+
finally {
|
|
159
|
+
request.socket.off('close', abortRequest);
|
|
160
|
+
response.off('close', abortResponse);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface DeploymentControlPlaneLimits {
|
|
2
|
+
readonly maxActivationRequestBytes: number;
|
|
3
|
+
readonly maxHistoryPageSize: number;
|
|
4
|
+
}
|
|
5
|
+
export declare const DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS: Readonly<DeploymentControlPlaneLimits>;
|
|
6
|
+
export declare function resolveDeploymentControlPlaneLimits(input?: Partial<DeploymentControlPlaneLimits>): Readonly<DeploymentControlPlaneLimits>;
|
|
7
|
+
//# sourceMappingURL=control-plane-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"control-plane-limits.d.ts","sourceRoot":"","sources":["../src/control-plane-limits.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,yBAAyB,EAAE,MAAM,CAAC;IAC3C,QAAQ,CAAC,kBAAkB,EAAE,MAAM,CAAC;CACrC;AAED,eAAO,MAAM,uCAAuC,EACpD,QAAQ,CAAC,4BAA4B,CAGnC,CAAC;AAEH,wBAAgB,mCAAmC,CACjD,KAAK,GAAE,OAAO,CAAC,4BAA4B,CAAM,GAChD,QAAQ,CAAC,4BAA4B,CAAC,CAcxC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS = Object.freeze({
|
|
2
|
+
maxActivationRequestBytes: 16 * 1024,
|
|
3
|
+
maxHistoryPageSize: 100,
|
|
4
|
+
});
|
|
5
|
+
export function resolveDeploymentControlPlaneLimits(input = {}) {
|
|
6
|
+
const limits = { ...DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS };
|
|
7
|
+
for (const key of Object.keys(limits)) {
|
|
8
|
+
const value = input[key];
|
|
9
|
+
if (value === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
if (!Number.isSafeInteger(value) || value < 1
|
|
12
|
+
|| value > DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS[key]) {
|
|
13
|
+
throw new RangeError(`${key} must be a positive safe integer no greater than the control-plane v1 maximum`);
|
|
14
|
+
}
|
|
15
|
+
limits[key] = value;
|
|
16
|
+
}
|
|
17
|
+
return Object.freeze(limits);
|
|
18
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
|
|
2
|
+
import { type DeploymentActivationRegistry } from './activation.js';
|
|
3
|
+
import { type DeploymentControlPlaneLimits } from './control-plane-limits.js';
|
|
4
|
+
import type { DeploymentAuthenticator, DeploymentIntake, DeploymentIntakeRequest, DeploymentIntakeResponse } from './types.js';
|
|
5
|
+
export type DeploymentControlPlaneAction = 'activate' | 'read-current-activation' | 'read-activation-history';
|
|
6
|
+
export interface DeploymentControlPlaneAuthorizationInput<Principal> {
|
|
7
|
+
readonly principal: Principal;
|
|
8
|
+
readonly action: DeploymentControlPlaneAction;
|
|
9
|
+
readonly target: ProtocolDeploymentReleaseTarget;
|
|
10
|
+
readonly signal?: AbortSignal;
|
|
11
|
+
}
|
|
12
|
+
export interface DeploymentControlPlaneAuthorizer<Principal> {
|
|
13
|
+
authorize(input: DeploymentControlPlaneAuthorizationInput<Principal>): Promise<boolean>;
|
|
14
|
+
}
|
|
15
|
+
export interface DeploymentControlPlaneRequest extends DeploymentIntakeRequest {
|
|
16
|
+
readonly method: string;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly query?: Readonly<Record<string, string | readonly string[] | undefined>>;
|
|
19
|
+
/** Set by transport adapters without consuming the request stream. */
|
|
20
|
+
readonly hasBody?: boolean;
|
|
21
|
+
}
|
|
22
|
+
export type DeploymentControlPlaneResponse = DeploymentIntakeResponse;
|
|
23
|
+
export interface DeploymentControlPlane {
|
|
24
|
+
handle(request: DeploymentControlPlaneRequest): Promise<DeploymentControlPlaneResponse>;
|
|
25
|
+
}
|
|
26
|
+
export interface DeploymentControlPlaneOptions<Principal> {
|
|
27
|
+
readonly intake: DeploymentIntake;
|
|
28
|
+
readonly activations: DeploymentActivationRegistry;
|
|
29
|
+
readonly authenticator: DeploymentAuthenticator<Principal>;
|
|
30
|
+
readonly authorizer: DeploymentControlPlaneAuthorizer<Principal>;
|
|
31
|
+
readonly limits?: Partial<DeploymentControlPlaneLimits>;
|
|
32
|
+
}
|
|
33
|
+
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
|
+
export declare function createDeploymentControlPlane<Principal>(options: DeploymentControlPlaneOptions<Principal>): DeploymentControlPlane;
|
|
35
|
+
//# sourceMappingURL=control-plane.d.ts.map
|
|
@@ -0,0 +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,EAGL,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;CACzD;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,CAkHxB"}
|