@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.
@@ -0,0 +1,200 @@
1
+ import { createProtocolSchemaValueParser, ProtocolSchemaValueError, validateProtocolDeploymentContract, } from '@hypequery/protocol';
2
+ import { resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
3
+ export class DeploymentDataPlaneError extends Error {
4
+ code;
5
+ path;
6
+ constructor(code, message, options = {}) {
7
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
8
+ this.name = 'DeploymentDataPlaneError';
9
+ this.code = code;
10
+ this.path = options.path;
11
+ }
12
+ }
13
+ function dataPlaneError(code, message, cause, path) {
14
+ return new DeploymentDataPlaneError(code, message, { cause, path });
15
+ }
16
+ function throwIfAborted(signal) {
17
+ if (signal?.aborted) {
18
+ throw dataPlaneError('HQ_DATA_PLANE_ABORTED', 'The data-plane request was aborted.', signal.reason);
19
+ }
20
+ }
21
+ function frozenRecord(entries) {
22
+ return Object.freeze(Object.assign(Object.create(null), entries));
23
+ }
24
+ function valueAtPath(input, path) {
25
+ let value = input;
26
+ for (const segment of path.split('.')) {
27
+ if (typeof value !== 'object' || value === null || Array.isArray(value)
28
+ || !Object.hasOwn(value, segment))
29
+ return undefined;
30
+ value = value[segment];
31
+ }
32
+ return value;
33
+ }
34
+ function sqlParameters(query, input, tenant) {
35
+ if (query.implementation.kind !== 'compiled-sql')
36
+ return Object.freeze({});
37
+ const values = Object.create(null);
38
+ for (const parameter of query.implementation.parameters) {
39
+ const value = parameter.source.kind === 'tenant'
40
+ ? tenant
41
+ : valueAtPath(input, parameter.source.path);
42
+ if (value === undefined) {
43
+ throw dataPlaneError('HQ_DATA_PLANE_INPUT_INVALID', 'A required compiled SQL binding is missing.', undefined, parameter.source.kind === 'input' ? `$.${parameter.source.path}` : '$.tenant');
44
+ }
45
+ values[parameter.name] = value;
46
+ }
47
+ return frozenRecord(values);
48
+ }
49
+ function routeTable(deployment, limits) {
50
+ const routes = new Map();
51
+ for (const query of deployment.queries) {
52
+ routes.set(`${query.endpoint.method}\0${query.endpoint.path}`, Object.freeze({
53
+ query,
54
+ input: createProtocolSchemaValueParser(query.input, { limits }),
55
+ output: createProtocolSchemaValueParser(query.output, { limits }),
56
+ }));
57
+ }
58
+ return routes;
59
+ }
60
+ function missing(values, actual) {
61
+ const available = new Set(actual ?? []);
62
+ return values.filter(value => !available.has(value));
63
+ }
64
+ export function createDeploymentDataPlane(options) {
65
+ let deployment;
66
+ try {
67
+ deployment = validateProtocolDeploymentContract(options.deployment);
68
+ }
69
+ catch (error) {
70
+ throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'The deployment data plane requires a valid deployment contract.', error);
71
+ }
72
+ const limits = resolveDeploymentDataPlaneLimits(options.limits);
73
+ const routes = routeTable(deployment, limits);
74
+ const paths = new Set(deployment.queries.map(query => query.endpoint.path));
75
+ return Object.freeze({
76
+ async execute(request) {
77
+ throwIfAborted(request.signal);
78
+ const route = routes.get(`${request.method}\0${request.path}`);
79
+ if (!route) {
80
+ if (paths.has(request.path)) {
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.');
84
+ }
85
+ const { query } = route;
86
+ let principal = null;
87
+ if (query.endpoint.access.kind === 'authenticated' && !options.authenticate) {
88
+ throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'An authenticator is required for this deployment route.');
89
+ }
90
+ if (options.authenticate
91
+ && (query.endpoint.access.kind === 'authenticated' || request.credentials !== undefined)) {
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
+ }
98
+ }
99
+ throwIfAborted(request.signal);
100
+ if (query.endpoint.access.kind === 'authenticated') {
101
+ if (!principal) {
102
+ throw dataPlaneError('HQ_DATA_PLANE_UNAUTHENTICATED', 'Authentication is required.');
103
+ }
104
+ if (missing(query.endpoint.access.roles, principal.roles).length > 0
105
+ || missing(query.endpoint.access.scopes, principal.scopes).length > 0) {
106
+ throw dataPlaneError('HQ_DATA_PLANE_FORBIDDEN', 'The principal lacks required access.');
107
+ }
108
+ }
109
+ let tenant;
110
+ if (query.endpoint.tenant.kind !== 'not-required') {
111
+ if (!options.resolveTenant) {
112
+ if (query.endpoint.tenant.kind === 'required') {
113
+ throw dataPlaneError('HQ_DATA_PLANE_CONFIGURATION', 'A tenant resolver is required for this deployment route.');
114
+ }
115
+ }
116
+ else {
117
+ try {
118
+ tenant = await options.resolveTenant({ principal, request, query });
119
+ }
120
+ catch (error) {
121
+ throw dataPlaneError('HQ_DATA_PLANE_FORBIDDEN', 'Tenant resolution failed.', error);
122
+ }
123
+ }
124
+ if (query.endpoint.tenant.kind === 'required' && (tenant === undefined || tenant === null)) {
125
+ throw dataPlaneError('HQ_DATA_PLANE_TENANT_REQUIRED', 'Tenant context is required.');
126
+ }
127
+ }
128
+ throwIfAborted(request.signal);
129
+ let input;
130
+ try {
131
+ input = route.input.parse(request.input);
132
+ }
133
+ catch (error) {
134
+ if (error instanceof ProtocolSchemaValueError) {
135
+ throw dataPlaneError('HQ_DATA_PLANE_INPUT_INVALID', 'The request input does not match the deployment schema.', error, error.path);
136
+ }
137
+ throw error;
138
+ }
139
+ const common = { query, input, principal, tenant, request, signal: request.signal };
140
+ let output;
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
+ }
170
+ }
171
+ catch (error) {
172
+ if (error instanceof DeploymentDataPlaneError)
173
+ throw error;
174
+ if (request.signal?.aborted) {
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);
178
+ }
179
+ try {
180
+ output = route.output.parse(output);
181
+ }
182
+ catch (error) {
183
+ if (error instanceof ProtocolSchemaValueError) {
184
+ throw dataPlaneError('HQ_DATA_PLANE_OUTPUT_INVALID', 'The query output does not match the deployment schema.', error, error.path);
185
+ }
186
+ throw error;
187
+ }
188
+ const publicCacheTtlMs = query.endpoint.access.kind === 'public'
189
+ && query.endpoint.tenant.kind === 'not-required'
190
+ && principal === null
191
+ ? query.endpoint.cacheTtlMs
192
+ : undefined;
193
+ return Object.freeze({
194
+ query: query.name,
195
+ output,
196
+ ...(publicCacheTtlMs === undefined ? {} : { cacheTtlMs: publicCacheTtlMs }),
197
+ });
198
+ },
199
+ });
200
+ }
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export { createFileSystemDeploymentActivationRegistry, DeploymentActivationError, } from './activation.js';
2
- export type { DeploymentActivationErrorCode, DeploymentActivationRegistry, DeploymentActivationRelease, DeploymentActivationRecord, DeploymentActivationRequest, DeploymentActivationResult, DeploymentReleaseReader, FileSystemDeploymentActivationRegistryOptions, } from './activation.js';
1
+ export { createFileSystemDeploymentActivationRegistry, DeploymentActivationError, validateDeploymentActivationRecord, } from './activation.js';
2
+ export type { DeploymentActivationErrorCode, DeploymentActivationHistoryPage, DeploymentActivationHistoryQuery, DeploymentActivationRegistry, DeploymentActivationRelease, DeploymentActivationRecord, DeploymentActivationRequest, DeploymentActivationResult, DeploymentReleaseReader, FileSystemDeploymentActivationRegistryOptions, } from './activation.js';
3
3
  export { DEPLOYMENT_BUNDLE_CONTRACT, DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle, } from './bundle.js';
4
4
  export type { VerifiedDeploymentBundle } from './bundle.js';
5
5
  export { DeploymentIntakeError, } from './errors.js';
@@ -7,7 +7,25 @@ export type { DeploymentIntakeErrorCode } from './errors.js';
7
7
  export { createFileSystemDeploymentSubmissionStore, FileSystemDeploymentStoreError, } from './filesystem-store.js';
8
8
  export type { FileSystemDeploymentStoreErrorCode, FileSystemDeploymentSubmissionStore, FileSystemDeploymentSubmissionStoreOptions, StoredDeploymentSubmission, } from './filesystem-store.js';
9
9
  export { createDeploymentIntake, } from './intake.js';
10
+ export { createDeploymentControlPlaneFetchHandler, createDeploymentControlPlaneNodeHandler, } from './control-plane-adapters.js';
11
+ export type { DeploymentControlPlaneFetchHandler, DeploymentControlPlaneNodeHandler, } from './control-plane-adapters.js';
12
+ export { createDeploymentControlPlane, } from './control-plane.js';
13
+ export type { DeploymentControlPlane, DeploymentControlPlaneAction, DeploymentControlPlaneAuthorizationInput, DeploymentControlPlaneAuthorizer, DeploymentControlPlaneErrorCode, DeploymentControlPlaneOptions, DeploymentControlPlaneRequest, DeploymentControlPlaneResponse, } from './control-plane.js';
14
+ export { DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS, resolveDeploymentControlPlaneLimits, } from './control-plane-limits.js';
15
+ export type { DeploymentControlPlaneLimits } from './control-plane-limits.js';
10
16
  export { DEFAULT_DEPLOYMENT_INTAKE_LIMITS, resolveDeploymentIntakeLimits, } from './limits.js';
11
17
  export type { DeploymentIntakeLimits } from './limits.js';
18
+ export { createNodeWorkerDeploymentRuntimeFactory, NodeDeploymentRuntimeError, } from './node-runtime-factory.js';
19
+ export type { NodeDeploymentRuntimeErrorCode, NodeDeploymentRuntimeFactoryOptions, } from './node-runtime-factory.js';
20
+ export { createDeploymentRuntimeMaterializer, DeploymentRuntimeMaterializationError, } from './runtime-materialization.js';
21
+ export type { DeploymentRuntimeArtifactSnapshot, DeploymentRuntimeMaterializationErrorCode, DeploymentRuntimeMaterializer, DeploymentRuntimeMaterializerOptions, DeploymentRuntimeQueryBinding, DeploymentRuntimeRelease, DeploymentRuntimeReleaseReader, DeploymentRuntimeSnapshot, } from './runtime-materialization.js';
22
+ export { createDeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorError, } from './runtime-supervisor.js';
23
+ export type { DeploymentRuntimeFactory, DeploymentRuntimeInstance, DeploymentRuntimeInstanceInvocation, DeploymentRuntimeInvocation, DeploymentRuntimeReconcileResult, DeploymentRuntimeStatus, DeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorErrorCode, DeploymentRuntimeSupervisorOptions, } from './runtime-supervisor.js';
12
24
  export type { DeploymentAuthenticationInput, DeploymentAuthenticator, DeploymentAuthorizationInput, DeploymentAuthorizer, DeploymentIntake, DeploymentIntakeOptions, DeploymentIntakeRequest, DeploymentIntakeResponse, DeploymentSubmissionResponse, DeploymentSubmissionStore, VerifiedDeploymentSubmission, } from './types.js';
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';
27
+ export { DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS, resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
28
+ export type { DeploymentDataPlaneLimits } from './data-plane-limits.js';
29
+ export { createDeploymentRuntimeSupervisorExecutor } from './data-plane-runtime.js';
30
+ export type { DeploymentRuntimeSupervisorExecutorOptions } from './data-plane-runtime.js';
13
31
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4CAA4C,EAC5C,yBAAyB,GAC1B,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,6BAA6B,EAC7B,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,6CAA6C,GAC9C,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,kCAAkC,EAClC,mCAAmC,EACnC,0CAA0C,EAC1C,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,YAAY,EACV,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,4BAA4B,EAC5B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,4CAA4C,EAC5C,yBAAyB,EACzB,kCAAkC,GACnC,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,6BAA6B,EAC7B,+BAA+B,EAC/B,gCAAgC,EAChC,4BAA4B,EAC5B,2BAA2B,EAC3B,0BAA0B,EAC1B,2BAA2B,EAC3B,0BAA0B,EAC1B,uBAAuB,EACvB,6CAA6C,GAC9C,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,0BAA0B,EAC1B,0BAA0B,EAC1B,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,wBAAwB,EAAE,MAAM,aAAa,CAAC;AAC5D,OAAO,EACL,qBAAqB,GACtB,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAC7D,OAAO,EACL,yCAAyC,EACzC,8BAA8B,GAC/B,MAAM,uBAAuB,CAAC;AAC/B,YAAY,EACV,kCAAkC,EAClC,mCAAmC,EACnC,0CAA0C,EAC1C,0BAA0B,GAC3B,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACL,sBAAsB,GACvB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,wCAAwC,EACxC,uCAAuC,GACxC,MAAM,6BAA6B,CAAC;AACrC,YAAY,EACV,kCAAkC,EAClC,iCAAiC,GAClC,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,4BAA4B,GAC7B,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,sBAAsB,EACtB,4BAA4B,EAC5B,wCAAwC,EACxC,gCAAgC,EAChC,+BAA+B,EAC/B,6BAA6B,EAC7B,6BAA6B,EAC7B,8BAA8B,GAC/B,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,uCAAuC,EACvC,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AACnC,YAAY,EAAE,4BAA4B,EAAE,MAAM,2BAA2B,CAAC;AAC9E,OAAO,EACL,gCAAgC,EAChC,6BAA6B,GAC9B,MAAM,aAAa,CAAC;AACrB,YAAY,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAC;AAC1D,OAAO,EACL,wCAAwC,EACxC,0BAA0B,GAC3B,MAAM,2BAA2B,CAAC;AACnC,YAAY,EACV,8BAA8B,EAC9B,mCAAmC,GACpC,MAAM,2BAA2B,CAAC;AACnC,OAAO,EACL,mCAAmC,EACnC,qCAAqC,GACtC,MAAM,8BAA8B,CAAC;AACtC,YAAY,EACV,iCAAiC,EACjC,yCAAyC,EACzC,6BAA6B,EAC7B,oCAAoC,EACpC,6BAA6B,EAC7B,wBAAwB,EACxB,8BAA8B,EAC9B,yBAAyB,GAC1B,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,iCAAiC,EACjC,gCAAgC,GACjC,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,wBAAwB,EACxB,yBAAyB,EACzB,mCAAmC,EACnC,2BAA2B,EAC3B,gCAAgC,EAChC,uBAAuB,EACvB,2BAA2B,EAC3B,oCAAoC,EACpC,kCAAkC,GACnC,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,6BAA6B,EAC7B,uBAAuB,EACvB,4BAA4B,EAC5B,oBAAoB,EACpB,gBAAgB,EAChB,uBAAuB,EACvB,uBAAuB,EACvB,wBAAwB,EACxB,4BAA4B,EAC5B,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,YAAY,CAAC;AACpB,OAAO,EACL,yBAAyB,EACzB,wBAAwB,GACzB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,mCAAmC,EACnC,mBAAmB,EACnB,sCAAsC,EACtC,4BAA4B,EAC5B,iCAAiC,EACjC,0BAA0B,EAC1B,4BAA4B,EAC5B,0BAA0B,EAC1B,yBAAyB,EACzB,8BAA8B,EAC9B,wCAAwC,EACxC,oCAAoC,GACrC,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,oCAAoC,EACpC,gCAAgC,GACjC,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC;AACxE,OAAO,EAAE,yCAAyC,EAAE,MAAM,yBAAyB,CAAC;AACpF,YAAY,EAAE,0CAA0C,EAAE,MAAM,yBAAyB,CAAC"}
package/dist/index.js CHANGED
@@ -1,6 +1,15 @@
1
- export { createFileSystemDeploymentActivationRegistry, DeploymentActivationError, } from './activation.js';
1
+ export { createFileSystemDeploymentActivationRegistry, DeploymentActivationError, validateDeploymentActivationRecord, } from './activation.js';
2
2
  export { DEPLOYMENT_BUNDLE_CONTRACT, DEPLOYMENT_BUNDLE_MANIFEST, verifyDeploymentBundle, } from './bundle.js';
3
3
  export { DeploymentIntakeError, } from './errors.js';
4
4
  export { createFileSystemDeploymentSubmissionStore, FileSystemDeploymentStoreError, } from './filesystem-store.js';
5
5
  export { createDeploymentIntake, } from './intake.js';
6
+ export { createDeploymentControlPlaneFetchHandler, createDeploymentControlPlaneNodeHandler, } from './control-plane-adapters.js';
7
+ export { createDeploymentControlPlane, } from './control-plane.js';
8
+ export { DEFAULT_DEPLOYMENT_CONTROL_PLANE_LIMITS, resolveDeploymentControlPlaneLimits, } from './control-plane-limits.js';
6
9
  export { DEFAULT_DEPLOYMENT_INTAKE_LIMITS, resolveDeploymentIntakeLimits, } from './limits.js';
10
+ export { createNodeWorkerDeploymentRuntimeFactory, NodeDeploymentRuntimeError, } from './node-runtime-factory.js';
11
+ export { createDeploymentRuntimeMaterializer, DeploymentRuntimeMaterializationError, } from './runtime-materialization.js';
12
+ export { createDeploymentRuntimeSupervisor, DeploymentRuntimeSupervisorError, } from './runtime-supervisor.js';
13
+ export { createDeploymentDataPlane, DeploymentDataPlaneError, } from './data-plane.js';
14
+ export { DEFAULT_DEPLOYMENT_DATA_PLANE_LIMITS, resolveDeploymentDataPlaneLimits, } from './data-plane-limits.js';
15
+ export { createDeploymentRuntimeSupervisorExecutor } from './data-plane-runtime.js';
@@ -0,0 +1,16 @@
1
+ import type { DeploymentRuntimeFactory } from './runtime-supervisor.js';
2
+ export type NodeDeploymentRuntimeErrorCode = 'HQ_NODE_RUNTIME_CONFIGURATION' | 'HQ_NODE_RUNTIME_UNSUPPORTED_ARTIFACT' | 'HQ_NODE_RUNTIME_INVALID_ARTIFACT' | 'HQ_NODE_RUNTIME_START_FAILED' | 'HQ_NODE_RUNTIME_WORKER_EXITED' | 'HQ_NODE_RUNTIME_INVOCATION_FAILED' | 'HQ_NODE_RUNTIME_ABORTED' | 'HQ_NODE_RUNTIME_CLOSED';
3
+ export declare class NodeDeploymentRuntimeError extends Error {
4
+ readonly code: NodeDeploymentRuntimeErrorCode;
5
+ constructor(code: NodeDeploymentRuntimeErrorCode, message: string, options?: {
6
+ readonly cause?: unknown;
7
+ });
8
+ }
9
+ export interface NodeDeploymentRuntimeFactoryOptions {
10
+ readonly temporaryDirectory?: string;
11
+ /** Worker import deadline from 1 through 300,000 milliseconds. */
12
+ readonly startupTimeoutMs?: number;
13
+ readonly onCleanupError?: (error: unknown, directory: string) => void;
14
+ }
15
+ export declare function createNodeWorkerDeploymentRuntimeFactory(options?: NodeDeploymentRuntimeFactoryOptions): DeploymentRuntimeFactory;
16
+ //# sourceMappingURL=node-runtime-factory.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"node-runtime-factory.d.ts","sourceRoot":"","sources":["../src/node-runtime-factory.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,wBAAwB,EAGzB,MAAM,yBAAyB,CAAC;AA+CjC,MAAM,MAAM,8BAA8B,GACtC,+BAA+B,GAC/B,sCAAsC,GACtC,kCAAkC,GAClC,8BAA8B,GAC9B,+BAA+B,GAC/B,mCAAmC,GACnC,yBAAyB,GACzB,wBAAwB,CAAC;AAE7B,qBAAa,0BAA2B,SAAQ,KAAK;IACnD,QAAQ,CAAC,IAAI,EAAE,8BAA8B,CAAC;gBAG5C,IAAI,EAAE,8BAA8B,EACpC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,mCAAmC;IAClD,QAAQ,CAAC,kBAAkB,CAAC,EAAE,MAAM,CAAC;IACrC,kEAAkE;IAClE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,KAAK,IAAI,CAAC;CACvE;AAoSD,wBAAgB,wCAAwC,CACtD,OAAO,GAAE,mCAAwC,GAChD,wBAAwB,CAuB1B"}
@@ -0,0 +1,274 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { chmod, lstat, mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises';
3
+ import { tmpdir } from 'node:os';
4
+ import path from 'node:path';
5
+ import { Worker } from 'node:worker_threads';
6
+ const DEFAULT_STARTUP_TIMEOUT_MS = 30_000;
7
+ const MAX_STARTUP_TIMEOUT_MS = 5 * 60_000;
8
+ const WORKER_SOURCE = [
9
+ "const { parentPort, workerData } = require('node:worker_threads');",
10
+ "const { pathToFileURL } = require('node:url');",
11
+ 'const handlers = new Map();',
12
+ 'function errorValue(error) {',
13
+ " return { name: error instanceof Error ? error.name : 'Error',",
14
+ " message: error instanceof Error ? error.message : String(error) };",
15
+ '}',
16
+ 'function resolveEntrypoint(module, entrypoint) {',
17
+ ' let value = module;',
18
+ " for (const segment of entrypoint.split('.')) value = value?.[segment];",
19
+ " if (typeof value !== 'function') throw new Error(`Runtime entrypoint is unavailable: ${entrypoint}`);",
20
+ ' return value;',
21
+ '}',
22
+ '(async () => {',
23
+ ' for (const artifact of workerData.artifacts) {',
24
+ ' const module = await import(pathToFileURL(artifact.path).href);',
25
+ ' for (const entrypoint of artifact.entrypoints) {',
26
+ ' handlers.set(entrypoint, resolveEntrypoint(module, entrypoint));',
27
+ ' }',
28
+ ' }',
29
+ " parentPort.postMessage({ kind: 'ready' });",
30
+ " parentPort.on('message', async message => {",
31
+ " if (message.kind === 'health') {",
32
+ " parentPort.postMessage({ kind: 'result', id: message.id, value: null });",
33
+ ' return;',
34
+ ' }',
35
+ " if (message.kind !== 'invoke') return;",
36
+ ' try {',
37
+ ' const handler = handlers.get(message.entrypoint);',
38
+ " if (!handler) throw new Error(`Runtime entrypoint is unavailable: ${message.entrypoint}`);",
39
+ ' const value = await handler(message.argument);',
40
+ " parentPort.postMessage({ kind: 'result', id: message.id, value });",
41
+ ' } catch (error) {',
42
+ " parentPort.postMessage({ kind: 'failure', id: message.id, error: errorValue(error) });",
43
+ ' }',
44
+ ' });',
45
+ '})().catch(error => {',
46
+ " parentPort.postMessage({ kind: 'fatal', error: errorValue(error) });",
47
+ '});',
48
+ ].join('\n');
49
+ export class NodeDeploymentRuntimeError extends Error {
50
+ code;
51
+ constructor(code, message, options = {}) {
52
+ super(message, options.cause === undefined ? undefined : { cause: options.cause });
53
+ this.name = 'NodeDeploymentRuntimeError';
54
+ this.code = code;
55
+ }
56
+ }
57
+ function nodeRuntimeError(code, message, cause) {
58
+ return new NodeDeploymentRuntimeError(code, message, { cause });
59
+ }
60
+ function startupTimeout(input) {
61
+ const value = input ?? DEFAULT_STARTUP_TIMEOUT_MS;
62
+ if (!Number.isSafeInteger(value) || value < 1 || value > MAX_STARTUP_TIMEOUT_MS) {
63
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_CONFIGURATION', `startupTimeoutMs must be between 1 and ${MAX_STARTUP_TIMEOUT_MS}.`);
64
+ }
65
+ return value;
66
+ }
67
+ function sha256(bytes) {
68
+ return createHash('sha256').update(bytes).digest('hex');
69
+ }
70
+ async function ensureTemporaryDirectory(input) {
71
+ const directory = path.resolve(input ?? tmpdir());
72
+ await mkdir(directory, { recursive: true, mode: 0o700 });
73
+ const stat = await lstat(directory);
74
+ if (stat.isSymbolicLink() || !stat.isDirectory()) {
75
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_CONFIGURATION', 'The Node runtime temporary directory must be a regular directory.');
76
+ }
77
+ return directory;
78
+ }
79
+ function remoteError(input) {
80
+ const message = typeof input?.message === 'string'
81
+ ? input.message.slice(0, 4096)
82
+ : 'The runtime worker failed without an error message.';
83
+ const error = new Error(message);
84
+ if (typeof input?.name === 'string')
85
+ error.name = input.name.slice(0, 128);
86
+ return error;
87
+ }
88
+ function workerInstance(worker, directory, ready) {
89
+ const pending = new Map();
90
+ let sequence = 0;
91
+ let closed = false;
92
+ let closePromise;
93
+ let terminalError;
94
+ function failPending(error) {
95
+ for (const call of pending.values()) {
96
+ call.cleanup();
97
+ call.reject(error);
98
+ }
99
+ pending.clear();
100
+ }
101
+ worker.on('message', (message) => {
102
+ if ((message.kind !== 'result' && message.kind !== 'failure')
103
+ || !Number.isSafeInteger(message.id))
104
+ return;
105
+ const call = pending.get(message.id);
106
+ if (!call)
107
+ return;
108
+ pending.delete(message.id);
109
+ call.cleanup();
110
+ if (message.kind === 'result')
111
+ call.resolve(message.value);
112
+ else
113
+ call.reject(nodeRuntimeError('HQ_NODE_RUNTIME_INVOCATION_FAILED', 'The Node deployment runtime invocation failed.', remoteError(message.error)));
114
+ });
115
+ worker.on('error', error => {
116
+ terminalError = nodeRuntimeError('HQ_NODE_RUNTIME_WORKER_EXITED', 'The Node deployment runtime worker failed.', error);
117
+ failPending(terminalError);
118
+ });
119
+ worker.on('exit', code => {
120
+ if (!closed) {
121
+ terminalError = nodeRuntimeError('HQ_NODE_RUNTIME_WORKER_EXITED', `The Node deployment runtime worker exited unexpectedly with code ${code}.`);
122
+ failPending(terminalError);
123
+ }
124
+ });
125
+ function call(kind, payload, signal) {
126
+ if (closed || terminalError) {
127
+ return Promise.reject(terminalError ?? nodeRuntimeError('HQ_NODE_RUNTIME_CLOSED', 'The Node deployment runtime worker is closed.'));
128
+ }
129
+ if (signal?.aborted) {
130
+ return Promise.reject(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'The Node deployment runtime invocation was aborted.', signal.reason));
131
+ }
132
+ const id = sequence;
133
+ sequence += 1;
134
+ return new Promise((resolve, reject) => {
135
+ const observeAbort = kind === 'health';
136
+ const abort = () => {
137
+ pending.delete(id);
138
+ reject(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'The Node deployment runtime health check was aborted.', signal?.reason));
139
+ };
140
+ const cleanup = () => signal?.removeEventListener('abort', abort);
141
+ pending.set(id, { resolve, reject, cleanup });
142
+ if (observeAbort)
143
+ signal?.addEventListener('abort', abort, { once: true });
144
+ try {
145
+ worker.postMessage({ kind, id, ...payload });
146
+ }
147
+ catch (error) {
148
+ pending.delete(id);
149
+ cleanup();
150
+ reject(nodeRuntimeError('HQ_NODE_RUNTIME_INVOCATION_FAILED', 'The Node deployment runtime invocation could not be sent.', error));
151
+ }
152
+ });
153
+ }
154
+ return {
155
+ ready,
156
+ async healthCheck({ signal }) {
157
+ await call('health', {}, signal);
158
+ },
159
+ invoke(input) {
160
+ return call('invoke', {
161
+ entrypoint: input.binding.entrypoint,
162
+ argument: input.argument,
163
+ }, input.signal);
164
+ },
165
+ async close() {
166
+ if (closePromise)
167
+ return closePromise;
168
+ closed = true;
169
+ const error = nodeRuntimeError('HQ_NODE_RUNTIME_CLOSED', 'The Node deployment runtime worker was closed.');
170
+ failPending(error);
171
+ closePromise = (async () => {
172
+ try {
173
+ await worker.terminate();
174
+ }
175
+ finally {
176
+ await rm(directory, { force: true, recursive: true });
177
+ }
178
+ })();
179
+ return closePromise;
180
+ },
181
+ };
182
+ }
183
+ async function startWorker(snapshot, directory, timeoutMs, signal) {
184
+ if (signal?.aborted) {
185
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'Node runtime startup was aborted.', signal.reason);
186
+ }
187
+ const artifacts = [];
188
+ for (const artifact of snapshot.artifacts) {
189
+ if (artifact.runtime !== 'node') {
190
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_UNSUPPORTED_ARTIFACT', `The Node runtime factory cannot load a ${artifact.runtime} artifact.`);
191
+ }
192
+ const bytes = artifact.read();
193
+ if (bytes.byteLength !== artifact.byteLength || sha256(bytes) !== artifact.artifactSha256) {
194
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_INVALID_ARTIFACT', 'Materialized Node runtime bytes do not match their identity.');
195
+ }
196
+ const artifactPath = path.join(directory, `${artifact.artifactSha256}.mjs`);
197
+ await writeFile(artifactPath, bytes, { flag: 'wx', mode: 0o600 });
198
+ artifacts.push(Object.freeze({ path: artifactPath, entrypoints: artifact.entrypoints }));
199
+ }
200
+ if (artifacts.length === 0) {
201
+ throw nodeRuntimeError('HQ_NODE_RUNTIME_UNSUPPORTED_ARTIFACT', 'The deployment snapshot contains no Node runtime artifacts.');
202
+ }
203
+ const worker = new Worker(WORKER_SOURCE, {
204
+ eval: true,
205
+ workerData: { artifacts },
206
+ });
207
+ let resolveReady;
208
+ let rejectReady;
209
+ const ready = new Promise((resolve, reject) => {
210
+ resolveReady = resolve;
211
+ rejectReady = reject;
212
+ });
213
+ const timer = setTimeout(() => {
214
+ rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime did not start before its deadline.'));
215
+ }, timeoutMs);
216
+ timer.unref();
217
+ const abort = () => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_ABORTED', 'Node runtime startup was aborted.', signal?.reason));
218
+ signal?.addEventListener('abort', abort, { once: true });
219
+ const startupMessage = (message) => {
220
+ if (message.kind === 'ready')
221
+ resolveReady();
222
+ if (message.kind === 'fatal')
223
+ rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime could not import its artifacts.', remoteError(message.error)));
224
+ };
225
+ const startupError = (error) => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', 'The Node deployment runtime worker failed during startup.', error));
226
+ const startupExit = (code) => rejectReady(nodeRuntimeError('HQ_NODE_RUNTIME_START_FAILED', `The Node deployment runtime worker exited during startup with code ${code}.`));
227
+ worker.on('message', startupMessage);
228
+ worker.once('error', startupError);
229
+ worker.once('exit', startupExit);
230
+ const instance = workerInstance(worker, directory, ready);
231
+ try {
232
+ await ready;
233
+ return instance;
234
+ }
235
+ catch (error) {
236
+ try {
237
+ await instance.close();
238
+ }
239
+ catch {
240
+ // Preserve the startup failure; cleanup can be retried by the factory.
241
+ }
242
+ throw error;
243
+ }
244
+ finally {
245
+ clearTimeout(timer);
246
+ signal?.removeEventListener('abort', abort);
247
+ worker.off('message', startupMessage);
248
+ worker.off('error', startupError);
249
+ worker.off('exit', startupExit);
250
+ }
251
+ }
252
+ export function createNodeWorkerDeploymentRuntimeFactory(options = {}) {
253
+ const timeoutMs = startupTimeout(options.startupTimeoutMs);
254
+ return Object.freeze({
255
+ async start(snapshot, input) {
256
+ const root = await ensureTemporaryDirectory(options.temporaryDirectory);
257
+ const directory = await mkdtemp(path.join(root, 'hypequery-node-runtime-'));
258
+ try {
259
+ await chmod(directory, 0o700);
260
+ return await startWorker(snapshot, directory, timeoutMs, input.signal);
261
+ }
262
+ catch (error) {
263
+ try {
264
+ await rm(directory, { force: true, recursive: true });
265
+ }
266
+ catch (cleanupError) {
267
+ options.onCleanupError?.(cleanupError, directory);
268
+ // Preserve the error that prevented a runtime from starting.
269
+ }
270
+ throw error;
271
+ }
272
+ },
273
+ });
274
+ }
@@ -0,0 +1,58 @@
1
+ import type { ProtocolDeploymentContract, ProtocolDeploymentReleaseEnvelope, ProtocolDeploymentReleaseTarget } from '@hypequery/protocol';
2
+ import type { DeploymentActivationRecord, DeploymentActivationRegistry } from './activation.js';
3
+ import { type VerifiedDeploymentBundle } from './bundle.js';
4
+ export type DeploymentRuntimeMaterializationErrorCode = 'HQ_RUNTIME_MATERIALIZATION_CONFIGURATION' | 'HQ_RUNTIME_MATERIALIZATION_ACTIVATION_UNAVAILABLE' | 'HQ_RUNTIME_MATERIALIZATION_RELEASE_NOT_FOUND' | 'HQ_RUNTIME_MATERIALIZATION_RELEASE_INVALID' | 'HQ_RUNTIME_MATERIALIZATION_ARTIFACT_INVALID' | 'HQ_RUNTIME_MATERIALIZATION_UNSTABLE_ACTIVATION';
5
+ export declare class DeploymentRuntimeMaterializationError extends Error {
6
+ readonly code: DeploymentRuntimeMaterializationErrorCode;
7
+ constructor(code: DeploymentRuntimeMaterializationErrorCode, message: string, options?: {
8
+ readonly cause?: unknown;
9
+ });
10
+ }
11
+ export interface DeploymentRuntimeRelease {
12
+ readonly release: ProtocolDeploymentReleaseEnvelope;
13
+ readonly releaseIdentity: string;
14
+ readonly bundle: VerifiedDeploymentBundle;
15
+ }
16
+ export interface DeploymentRuntimeReleaseReader {
17
+ /** Return only a fully revalidated accepted release and its closed bundle. */
18
+ read(releaseIdentity: string): Promise<DeploymentRuntimeRelease | undefined>;
19
+ }
20
+ export interface DeploymentRuntimeArtifactSnapshot {
21
+ readonly runtime: 'node' | 'python';
22
+ readonly artifactSha256: string;
23
+ readonly byteLength: number;
24
+ readonly entrypoints: readonly string[];
25
+ /** Returns a fresh copy so callers cannot mutate the materialized bytes. */
26
+ read(): Uint8Array;
27
+ }
28
+ export interface DeploymentRuntimeQueryBinding {
29
+ readonly query: string;
30
+ readonly runtime: 'node' | 'python';
31
+ readonly artifactSha256: string;
32
+ readonly entrypoint: string;
33
+ }
34
+ export interface DeploymentRuntimeSnapshot {
35
+ readonly target: ProtocolDeploymentReleaseTarget;
36
+ readonly activation: DeploymentActivationRecord;
37
+ readonly release: ProtocolDeploymentReleaseEnvelope;
38
+ readonly releaseIdentity: string;
39
+ readonly bundleIdentity: string;
40
+ readonly deployment: ProtocolDeploymentContract;
41
+ readonly artifacts: readonly DeploymentRuntimeArtifactSnapshot[];
42
+ readonly queries: readonly DeploymentRuntimeQueryBinding[];
43
+ }
44
+ export interface DeploymentRuntimeMaterializer {
45
+ /**
46
+ * Materialize the current activation and confirm it remained current while
47
+ * its bytes were copied. Returns undefined when the target has no activation.
48
+ */
49
+ current(target: ProtocolDeploymentReleaseTarget): Promise<DeploymentRuntimeSnapshot | undefined>;
50
+ }
51
+ export interface DeploymentRuntimeMaterializerOptions {
52
+ readonly activations: DeploymentActivationRegistry;
53
+ readonly releases: DeploymentRuntimeReleaseReader;
54
+ /** Activation-stability attempts from 1 through 16. */
55
+ readonly maxStabilityAttempts?: number;
56
+ }
57
+ export declare function createDeploymentRuntimeMaterializer(options: DeploymentRuntimeMaterializerOptions): DeploymentRuntimeMaterializer;
58
+ //# sourceMappingURL=runtime-materialization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-materialization.d.ts","sourceRoot":"","sources":["../src/runtime-materialization.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,0BAA0B,EAC1B,iCAAiC,EACjC,+BAA+B,EAChC,MAAM,qBAAqB,CAAC;AAG7B,OAAO,KAAK,EACV,0BAA0B,EAC1B,4BAA4B,EAC7B,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAEL,KAAK,wBAAwB,EAC9B,MAAM,aAAa,CAAC;AAKrB,MAAM,MAAM,yCAAyC,GACjD,0CAA0C,GAC1C,mDAAmD,GACnD,8CAA8C,GAC9C,4CAA4C,GAC5C,6CAA6C,GAC7C,gDAAgD,CAAC;AAErD,qBAAa,qCAAsC,SAAQ,KAAK;IAC9D,QAAQ,CAAC,IAAI,EAAE,yCAAyC,CAAC;gBAGvD,IAAI,EAAE,yCAAyC,EAC/C,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAM7C;AAED,MAAM,WAAW,wBAAwB;IACvC,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,EAAE,wBAAwB,CAAC;CAC3C;AAED,MAAM,WAAW,8BAA8B;IAC7C,8EAA8E;IAC9E,IAAI,CAAC,eAAe,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,GAAG,SAAS,CAAC,CAAC;CAC9E;AAED,MAAM,WAAW,iCAAiC;IAChD,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,4EAA4E;IAC5E,IAAI,IAAI,UAAU,CAAC;CACpB;AAED,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,QAAQ,CAAC;IACpC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;CAC7B;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,MAAM,EAAE,+BAA+B,CAAC;IACjD,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;IAChD,QAAQ,CAAC,OAAO,EAAE,iCAAiC,CAAC;IACpD,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,UAAU,EAAE,0BAA0B,CAAC;IAChD,QAAQ,CAAC,SAAS,EAAE,SAAS,iCAAiC,EAAE,CAAC;IACjE,QAAQ,CAAC,OAAO,EAAE,SAAS,6BAA6B,EAAE,CAAC;CAC5D;AAED,MAAM,WAAW,6BAA6B;IAC5C;;;OAGG;IACH,OAAO,CAAC,MAAM,EAAE,+BAA+B,GAAG,OAAO,CAAC,yBAAyB,GAAG,SAAS,CAAC,CAAC;CAClG;AAED,MAAM,WAAW,oCAAoC;IACnD,QAAQ,CAAC,WAAW,EAAE,4BAA4B,CAAC;IACnD,QAAQ,CAAC,QAAQ,EAAE,8BAA8B,CAAC;IAClD,uDAAuD;IACvD,QAAQ,CAAC,oBAAoB,CAAC,EAAE,MAAM,CAAC;CACxC;AA4LD,wBAAgB,mCAAmC,CACjD,OAAO,EAAE,oCAAoC,GAC5C,6BAA6B,CA+C/B"}