@hypequery/deployment 0.0.0-canary-20260719200737 → 0.1.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 +2 -182
- package/dist/index.d.ts +0 -16
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +0 -8
- package/package.json +3 -3
- package/dist/activation.d.ts +0 -62
- package/dist/activation.d.ts.map +0 -1
- package/dist/activation.js +0 -611
- package/dist/control-plane-adapters.d.ts +0 -7
- package/dist/control-plane-adapters.d.ts.map +0 -1
- package/dist/control-plane-adapters.js +0 -163
- package/dist/control-plane-limits.d.ts +0 -7
- package/dist/control-plane-limits.d.ts.map +0 -1
- package/dist/control-plane-limits.js +0 -18
- package/dist/control-plane.d.ts +0 -35
- package/dist/control-plane.d.ts.map +0 -1
- package/dist/control-plane.js +0 -362
- package/dist/filesystem-store.d.ts +0 -25
- package/dist/filesystem-store.d.ts.map +0 -1
- package/dist/filesystem-store.js +0 -418
- package/dist/node-runtime-factory.d.ts +0 -16
- package/dist/node-runtime-factory.d.ts.map +0 -1
- package/dist/node-runtime-factory.js +0 -274
- package/dist/runtime-materialization.d.ts +0 -58
- package/dist/runtime-materialization.d.ts.map +0 -1
- package/dist/runtime-materialization.js +0 -187
- package/dist/runtime-supervisor.d.ts +0 -70
- package/dist/runtime-supervisor.d.ts.map +0 -1
- package/dist/runtime-supervisor.js +0 -254
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
const DEFAULT_DRAIN_TIMEOUT_MS = 30_000;
|
|
2
|
-
const DEFAULT_RECONCILE_ATTEMPTS = 4;
|
|
3
|
-
const MAX_DRAIN_TIMEOUT_MS = 5 * 60_000;
|
|
4
|
-
const MAX_RECONCILE_ATTEMPTS = 16;
|
|
5
|
-
export class DeploymentRuntimeSupervisorError extends Error {
|
|
6
|
-
code;
|
|
7
|
-
constructor(code, message, options = {}) {
|
|
8
|
-
super(message, options.cause === undefined ? undefined : { cause: options.cause });
|
|
9
|
-
this.name = 'DeploymentRuntimeSupervisorError';
|
|
10
|
-
this.code = code;
|
|
11
|
-
}
|
|
12
|
-
}
|
|
13
|
-
function supervisorError(code, message, cause) {
|
|
14
|
-
return new DeploymentRuntimeSupervisorError(code, message, { cause });
|
|
15
|
-
}
|
|
16
|
-
function boundedInteger(input, fallback, maximum, name, allowZero = false) {
|
|
17
|
-
const value = input ?? fallback;
|
|
18
|
-
if (!Number.isSafeInteger(value) || value < (allowZero ? 0 : 1) || value > maximum) {
|
|
19
|
-
throw supervisorError('HQ_RUNTIME_SUPERVISOR_CONFIGURATION', `${name} must be an integer between ${allowZero ? 0 : 1} and ${maximum}.`);
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
}
|
|
23
|
-
function targetKey(target) {
|
|
24
|
-
return JSON.stringify([target.project, target.environment]);
|
|
25
|
-
}
|
|
26
|
-
function sameTarget(left, right) {
|
|
27
|
-
return left.project === right.project && left.environment === right.environment;
|
|
28
|
-
}
|
|
29
|
-
function status(generation) {
|
|
30
|
-
const snapshot = generation.snapshot;
|
|
31
|
-
return Object.freeze({
|
|
32
|
-
target: snapshot.target,
|
|
33
|
-
activationRevision: snapshot.activation.revision,
|
|
34
|
-
releaseIdentity: snapshot.releaseIdentity,
|
|
35
|
-
bundleIdentity: snapshot.bundleIdentity,
|
|
36
|
-
});
|
|
37
|
-
}
|
|
38
|
-
function throwIfAborted(signal) {
|
|
39
|
-
if (signal?.aborted) {
|
|
40
|
-
throw supervisorError('HQ_RUNTIME_ABORTED', 'The runtime operation was aborted.', signal.reason);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function binding(snapshot, queryName) {
|
|
44
|
-
const query = snapshot.deployment.queries.find(candidate => candidate.name === queryName);
|
|
45
|
-
if (!query) {
|
|
46
|
-
throw supervisorError('HQ_RUNTIME_QUERY_NOT_FOUND', `Runtime query not found: ${queryName}`);
|
|
47
|
-
}
|
|
48
|
-
if (query.implementation.kind !== 'runtime-reference') {
|
|
49
|
-
throw supervisorError('HQ_RUNTIME_QUERY_NOT_EXECUTABLE', `Query is portable and does not use a supervised runtime: ${queryName}`);
|
|
50
|
-
}
|
|
51
|
-
const resolved = snapshot.queries.find(candidate => candidate.query === queryName);
|
|
52
|
-
if (!resolved) {
|
|
53
|
-
throw supervisorError('HQ_RUNTIME_QUERY_NOT_EXECUTABLE', `Runtime query binding is unavailable: ${queryName}`);
|
|
54
|
-
}
|
|
55
|
-
return resolved;
|
|
56
|
-
}
|
|
57
|
-
export function createDeploymentRuntimeSupervisor(options) {
|
|
58
|
-
const drainTimeoutMs = boundedInteger(options.drainTimeoutMs, DEFAULT_DRAIN_TIMEOUT_MS, MAX_DRAIN_TIMEOUT_MS, 'drainTimeoutMs', true);
|
|
59
|
-
const maxReconcileAttempts = boundedInteger(options.maxReconcileAttempts, DEFAULT_RECONCILE_ATTEMPTS, MAX_RECONCILE_ATTEMPTS, 'maxReconcileAttempts');
|
|
60
|
-
const active = new Map();
|
|
61
|
-
const updates = new Map();
|
|
62
|
-
const background = new Set();
|
|
63
|
-
const backgroundFailures = [];
|
|
64
|
-
let closed = false;
|
|
65
|
-
let closePromise;
|
|
66
|
-
function ensureOpen() {
|
|
67
|
-
if (closed)
|
|
68
|
-
throw supervisorError('HQ_RUNTIME_SUPERVISOR_CLOSED', 'Runtime supervisor is closed.');
|
|
69
|
-
}
|
|
70
|
-
async function drain(generation) {
|
|
71
|
-
if (generation.draining)
|
|
72
|
-
return;
|
|
73
|
-
generation.draining = true;
|
|
74
|
-
if (generation.inFlight > 0) {
|
|
75
|
-
await new Promise(resolve => {
|
|
76
|
-
let settled = false;
|
|
77
|
-
const finish = () => {
|
|
78
|
-
if (settled)
|
|
79
|
-
return;
|
|
80
|
-
settled = true;
|
|
81
|
-
clearTimeout(timer);
|
|
82
|
-
generation.drained = undefined;
|
|
83
|
-
resolve();
|
|
84
|
-
};
|
|
85
|
-
generation.drained = finish;
|
|
86
|
-
const timer = setTimeout(finish, drainTimeoutMs);
|
|
87
|
-
timer.unref();
|
|
88
|
-
});
|
|
89
|
-
}
|
|
90
|
-
await generation.instance.close();
|
|
91
|
-
}
|
|
92
|
-
function drainInBackground(generation) {
|
|
93
|
-
const operation = drain(generation).catch(error => {
|
|
94
|
-
backgroundFailures.push(error);
|
|
95
|
-
options.onBackgroundError?.(error);
|
|
96
|
-
});
|
|
97
|
-
background.add(operation);
|
|
98
|
-
void operation.finally(() => background.delete(operation));
|
|
99
|
-
}
|
|
100
|
-
async function safeClose(instance) {
|
|
101
|
-
try {
|
|
102
|
-
await instance.close();
|
|
103
|
-
}
|
|
104
|
-
catch (error) {
|
|
105
|
-
backgroundFailures.push(error);
|
|
106
|
-
options.onBackgroundError?.(error);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
async function performReconcile(target, signal) {
|
|
110
|
-
for (let attempt = 0; attempt < maxReconcileAttempts; attempt += 1) {
|
|
111
|
-
ensureOpen();
|
|
112
|
-
throwIfAborted(signal);
|
|
113
|
-
const snapshot = await options.materializer.current(target);
|
|
114
|
-
ensureOpen();
|
|
115
|
-
throwIfAborted(signal);
|
|
116
|
-
const key = targetKey(target);
|
|
117
|
-
const existing = active.get(key);
|
|
118
|
-
if (!snapshot) {
|
|
119
|
-
if (!existing)
|
|
120
|
-
return Object.freeze({ status: 'no-active-release' });
|
|
121
|
-
active.delete(key);
|
|
122
|
-
drainInBackground(existing);
|
|
123
|
-
return Object.freeze({ status: 'deactivated', previous: status(existing) });
|
|
124
|
-
}
|
|
125
|
-
if (!sameTarget(snapshot.target, target)) {
|
|
126
|
-
throw supervisorError('HQ_RUNTIME_START_FAILED', 'The materialized runtime snapshot does not match the requested target.');
|
|
127
|
-
}
|
|
128
|
-
if (existing?.snapshot.activation.revision === snapshot.activation.revision) {
|
|
129
|
-
return Object.freeze({ status: 'already-current', runtime: status(existing) });
|
|
130
|
-
}
|
|
131
|
-
let candidate;
|
|
132
|
-
try {
|
|
133
|
-
candidate = await options.factory.start(snapshot, { signal });
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
if (signal?.aborted) {
|
|
137
|
-
throw supervisorError('HQ_RUNTIME_ABORTED', 'The runtime operation was aborted while starting a candidate.', error);
|
|
138
|
-
}
|
|
139
|
-
if (error instanceof DeploymentRuntimeSupervisorError)
|
|
140
|
-
throw error;
|
|
141
|
-
throw supervisorError('HQ_RUNTIME_START_FAILED', 'The candidate deployment runtime could not be started.', error);
|
|
142
|
-
}
|
|
143
|
-
try {
|
|
144
|
-
ensureOpen();
|
|
145
|
-
throwIfAborted(signal);
|
|
146
|
-
await candidate.healthCheck({ signal });
|
|
147
|
-
}
|
|
148
|
-
catch (error) {
|
|
149
|
-
await safeClose(candidate);
|
|
150
|
-
if (error instanceof DeploymentRuntimeSupervisorError)
|
|
151
|
-
throw error;
|
|
152
|
-
throw supervisorError('HQ_RUNTIME_HEALTH_FAILED', 'The candidate deployment runtime did not become ready.', error);
|
|
153
|
-
}
|
|
154
|
-
let confirmed;
|
|
155
|
-
try {
|
|
156
|
-
ensureOpen();
|
|
157
|
-
throwIfAborted(signal);
|
|
158
|
-
confirmed = await options.materializer.current(target);
|
|
159
|
-
ensureOpen();
|
|
160
|
-
throwIfAborted(signal);
|
|
161
|
-
}
|
|
162
|
-
catch (error) {
|
|
163
|
-
await safeClose(candidate);
|
|
164
|
-
throw error;
|
|
165
|
-
}
|
|
166
|
-
if (confirmed?.activation.revision !== snapshot.activation.revision) {
|
|
167
|
-
await safeClose(candidate);
|
|
168
|
-
continue;
|
|
169
|
-
}
|
|
170
|
-
const generation = {
|
|
171
|
-
snapshot,
|
|
172
|
-
instance: candidate,
|
|
173
|
-
inFlight: 0,
|
|
174
|
-
draining: false,
|
|
175
|
-
};
|
|
176
|
-
const previous = active.get(key);
|
|
177
|
-
active.set(key, generation);
|
|
178
|
-
if (previous)
|
|
179
|
-
drainInBackground(previous);
|
|
180
|
-
return Object.freeze({ status: 'activated', runtime: status(generation) });
|
|
181
|
-
}
|
|
182
|
-
throw supervisorError('HQ_RUNTIME_RECONCILE_UNSTABLE', 'Deployment activation changed repeatedly while starting a runtime.');
|
|
183
|
-
}
|
|
184
|
-
function serialize(key, operation) {
|
|
185
|
-
const previous = updates.get(key) ?? Promise.resolve();
|
|
186
|
-
const current = previous.catch(() => undefined).then(operation);
|
|
187
|
-
const tracked = current.finally(() => {
|
|
188
|
-
if (updates.get(key) === tracked)
|
|
189
|
-
updates.delete(key);
|
|
190
|
-
});
|
|
191
|
-
updates.set(key, tracked);
|
|
192
|
-
return tracked;
|
|
193
|
-
}
|
|
194
|
-
return Object.freeze({
|
|
195
|
-
async reconcile(target, reconcileOptions = {}) {
|
|
196
|
-
ensureOpen();
|
|
197
|
-
return await serialize(targetKey(target), () => performReconcile(target, reconcileOptions.signal));
|
|
198
|
-
},
|
|
199
|
-
async invoke(input) {
|
|
200
|
-
ensureOpen();
|
|
201
|
-
throwIfAborted(input.signal);
|
|
202
|
-
const generation = active.get(targetKey(input.target));
|
|
203
|
-
if (!generation) {
|
|
204
|
-
throw supervisorError('HQ_RUNTIME_NOT_READY', 'No deployment runtime is ready for target.');
|
|
205
|
-
}
|
|
206
|
-
const resolved = binding(generation.snapshot, input.query);
|
|
207
|
-
generation.inFlight += 1;
|
|
208
|
-
try {
|
|
209
|
-
return await generation.instance.invoke({
|
|
210
|
-
query: input.query,
|
|
211
|
-
binding: resolved,
|
|
212
|
-
argument: input.argument,
|
|
213
|
-
signal: input.signal,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
catch (error) {
|
|
217
|
-
if (error instanceof DeploymentRuntimeSupervisorError)
|
|
218
|
-
throw error;
|
|
219
|
-
throw supervisorError('HQ_RUNTIME_INVOCATION_FAILED', 'The deployment runtime invocation failed.', error);
|
|
220
|
-
}
|
|
221
|
-
finally {
|
|
222
|
-
generation.inFlight -= 1;
|
|
223
|
-
if (generation.draining && generation.inFlight === 0)
|
|
224
|
-
generation.drained?.();
|
|
225
|
-
}
|
|
226
|
-
},
|
|
227
|
-
status(target) {
|
|
228
|
-
const generation = active.get(targetKey(target));
|
|
229
|
-
return generation ? status(generation) : undefined;
|
|
230
|
-
},
|
|
231
|
-
close() {
|
|
232
|
-
if (closePromise)
|
|
233
|
-
return closePromise;
|
|
234
|
-
closed = true;
|
|
235
|
-
closePromise = (async () => {
|
|
236
|
-
await Promise.allSettled([...updates.values()]);
|
|
237
|
-
const generations = [...active.values()];
|
|
238
|
-
active.clear();
|
|
239
|
-
const results = await Promise.allSettled(generations.map(generation => drain(generation)));
|
|
240
|
-
await Promise.allSettled([...background]);
|
|
241
|
-
const failures = [
|
|
242
|
-
...backgroundFailures,
|
|
243
|
-
...results
|
|
244
|
-
.filter((result) => result.status === 'rejected')
|
|
245
|
-
.map(result => result.reason),
|
|
246
|
-
];
|
|
247
|
-
if (failures.length > 0) {
|
|
248
|
-
throw new AggregateError(failures, 'One or more deployment runtimes could not be closed.');
|
|
249
|
-
}
|
|
250
|
-
})();
|
|
251
|
-
return closePromise;
|
|
252
|
-
},
|
|
253
|
-
});
|
|
254
|
-
}
|