blob: 8c74c900e3cfc2bb6b35653018b70fcd1a042b05 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import type { DirectRequestOperationKey, RuntimeHostConnection } from '@maka/runtime-host/client';
import type {
SessionCatalogItem,
SessionCatalogProjection,
SessionUpdateResult,
} from '@maka/runtime-host/protocol';
const MAX_UPDATE_ATTEMPTS = 3;
type RuntimeHostSessionUpdateErrorReason =
| 'invalid_projection'
| 'not_found'
| 'unsupported_session_projection'
| 'revision_conflict';
export class RuntimeHostSessionUpdateError extends Error {
readonly name = 'RuntimeHostSessionUpdateError';
constructor(
readonly operation: DirectRequestOperationKey,
readonly reason: RuntimeHostSessionUpdateErrorReason,
readonly sessionId: string,
readonly attempts?: number,
) {
super(`Runtime Host Session update failed for session ${sessionId}: ${reason}`);
}
}
type RuntimeHostSessionUpdateConnection = Pick<RuntimeHostConnection, 'request'>;
export async function getRuntimeHostSession(
connection: RuntimeHostSessionUpdateConnection,
sessionId: string,
): Promise<SessionCatalogProjection | null> {
const result = await connection.request('session.catalog.query', { kind: 'get', sessionId });
if (result.kind !== 'session') {
throw new RuntimeHostSessionUpdateError(
'session.catalog.query',
'invalid_projection',
sessionId,
);
}
return result.session === null
? null
: requireRuntimeHostSessionProjection(result.session, 'session.catalog.query');
}
export async function updateRuntimeHostSession(
connection: RuntimeHostSessionUpdateConnection,
sessionId: string,
update: (current: SessionCatalogProjection) => Promise<SessionUpdateResult>,
options: {
readonly operation: DirectRequestOperationKey;
readonly assertRequestAllowed?: () => void;
},
): Promise<SessionCatalogProjection> {
for (let attempt = 0; attempt < MAX_UPDATE_ATTEMPTS; attempt += 1) {
options.assertRequestAllowed?.();
const current = await getRuntimeHostSession(connection, sessionId);
if (!current) {
throw new RuntimeHostSessionUpdateError('session.catalog.query', 'not_found', sessionId);
}
options.assertRequestAllowed?.();
const result = await update(current);
if (result.kind === 'committed') {
return requireRuntimeHostSessionProjection(result.session, options.operation);
}
}
throw new RuntimeHostSessionUpdateError(
options.operation,
'revision_conflict',
sessionId,
MAX_UPDATE_ATTEMPTS,
);
}
export function requireRuntimeHostSessionProjection(
session: SessionCatalogItem,
operation: DirectRequestOperationKey = 'session.catalog.query',
): SessionCatalogProjection {
if (!('kind' in session)) return session;
throw new RuntimeHostSessionUpdateError(operation, 'unsupported_session_projection', session.id);
}