feat(core): add session snapshot and revert system (#33226)
This commit is contained in:
parent
25c6abc31a
commit
9bb5370205
42 changed files with 2311 additions and 365 deletions
|
|
@ -61,6 +61,16 @@ export class SessionNotFoundError extends Schema.TaggedErrorClass<SessionNotFoun
|
|||
{ httpApiStatus: 404 },
|
||||
) {}
|
||||
|
||||
export class MessageNotFoundError extends Schema.TaggedErrorClass<MessageNotFoundError>()(
|
||||
"MessageNotFoundError",
|
||||
{
|
||||
sessionID: Schema.String,
|
||||
messageID: Schema.String,
|
||||
message: Schema.String,
|
||||
},
|
||||
{ httpApiStatus: 404 },
|
||||
) {}
|
||||
|
||||
export class InvalidCursorError extends Schema.TaggedErrorClass<InvalidCursorError>()(
|
||||
"InvalidCursorError",
|
||||
{ message: Schema.String },
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import {
|
|||
ConflictError,
|
||||
InvalidCursorError,
|
||||
InvalidRequestError,
|
||||
MessageNotFoundError,
|
||||
ServiceUnavailableError,
|
||||
SessionNotFoundError,
|
||||
UnknownError,
|
||||
|
|
@ -223,6 +224,40 @@ export const SessionGroup = HttpApiGroup.make("server.session")
|
|||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.revert.stage", "/api/session/:sessionID/revert/stage", {
|
||||
params: { sessionID: SessionV2.ID },
|
||||
payload: Schema.Struct({ messageID: SessionMessage.ID, files: Schema.Boolean.pipe(Schema.optional) }),
|
||||
success: Schema.Struct({ data: SessionV2.RevertState }),
|
||||
error: [MessageNotFoundError, SessionNotFoundError, UnknownError],
|
||||
})
|
||||
.middleware(SessionLocationMiddleware)
|
||||
.annotateMerge(
|
||||
OpenApi.annotations({
|
||||
identifier: "v2.session.revert.stage",
|
||||
summary: "Stage session revert",
|
||||
description: "Stage or move a reversible session boundary and optionally apply its file changes.",
|
||||
}),
|
||||
),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.revert.clear", "/api/session/:sessionID/revert/clear", {
|
||||
params: { sessionID: SessionV2.ID },
|
||||
success: HttpApiSchema.NoContent,
|
||||
error: [SessionNotFoundError, UnknownError],
|
||||
})
|
||||
.middleware(SessionLocationMiddleware)
|
||||
.annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.clear", summary: "Clear staged revert" })),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.post("session.revert.commit", "/api/session/:sessionID/revert/commit", {
|
||||
params: { sessionID: SessionV2.ID },
|
||||
success: HttpApiSchema.NoContent,
|
||||
error: SessionNotFoundError,
|
||||
})
|
||||
.middleware(SessionLocationMiddleware)
|
||||
.annotateMerge(OpenApi.annotations({ identifier: "v2.session.revert.commit", summary: "Commit staged revert" })),
|
||||
)
|
||||
.add(
|
||||
HttpApiEndpoint.get("session.context", "/api/session/:sessionID/context", {
|
||||
params: { sessionID: SessionV2.ID },
|
||||
|
|
|
|||
|
|
@ -58,14 +58,7 @@ export const MessageHandler = HttpApiBuilder.group(Api, "server.message", (handl
|
|||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to decode session message").pipe(
|
||||
Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }),
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
new UnknownError({
|
||||
message: "Unexpected server error. Check server logs for details.",
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.andThen(Effect.fail(new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }))),
|
||||
)
|
||||
}),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import { SessionsCursor } from "../groups/session"
|
|||
import {
|
||||
ConflictError,
|
||||
InvalidCursorError,
|
||||
MessageNotFoundError,
|
||||
ServiceUnavailableError,
|
||||
SessionNotFoundError,
|
||||
UnknownError,
|
||||
|
|
@ -205,6 +206,90 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.revert.stage",
|
||||
Effect.fn(function* (ctx) {
|
||||
return {
|
||||
data: yield* session.revert.stage({ ...ctx.params, ...ctx.payload }).pipe(
|
||||
Effect.catchTag(
|
||||
"Session.NotFoundError",
|
||||
(error) =>
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
Effect.catchTag(
|
||||
"Session.MessageNotFoundError",
|
||||
(error) =>
|
||||
new MessageNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
messageID: error.messageID,
|
||||
message: `Message not found: ${error.messageID}`,
|
||||
}),
|
||||
),
|
||||
Effect.catchTag("Snapshot.Error", (error) => {
|
||||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to stage session revert", { cause: error }).pipe(
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
new UnknownError({
|
||||
message: "Unexpected server error. Check server logs for details.",
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.revert.clear",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* session.revert.clear(ctx.params.sessionID).pipe(
|
||||
Effect.catchTag(
|
||||
"Session.NotFoundError",
|
||||
(error) =>
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
Effect.catchTag("Snapshot.Error", (error) => {
|
||||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to clear session revert", { cause: error }).pipe(
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
new UnknownError({
|
||||
message: "Unexpected server error. Check server logs for details.",
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
}),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.revert.commit",
|
||||
Effect.fn(function* (ctx) {
|
||||
yield* session.revert.commit(ctx.params.sessionID).pipe(
|
||||
Effect.catchTag(
|
||||
"Session.NotFoundError",
|
||||
(error) =>
|
||||
new SessionNotFoundError({
|
||||
sessionID: error.sessionID,
|
||||
message: `Session not found: ${error.sessionID}`,
|
||||
}),
|
||||
),
|
||||
)
|
||||
return HttpApiSchema.NoContent.make()
|
||||
}),
|
||||
)
|
||||
.handle(
|
||||
"session.context",
|
||||
Effect.fn(function* (ctx) {
|
||||
|
|
@ -222,14 +307,7 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
|||
const ref = `err_${crypto.randomUUID().slice(0, 8)}`
|
||||
return Effect.logError("failed to decode session message").pipe(
|
||||
Effect.annotateLogs({ ref, sessionID: error.sessionID, messageID: error.messageID }),
|
||||
Effect.andThen(
|
||||
Effect.fail(
|
||||
new UnknownError({
|
||||
message: "Unexpected server error. Check server logs for details.",
|
||||
ref,
|
||||
}),
|
||||
),
|
||||
),
|
||||
Effect.andThen(Effect.fail(new UnknownError({ message: "Unexpected server error. Check server logs for details.", ref }))),
|
||||
)
|
||||
}),
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue