feat(session): add durable session archival
This commit is contained in:
parent
a6761d4fe0
commit
08e616bcad
9 changed files with 75 additions and 1 deletions
|
|
@ -226,6 +226,7 @@ export interface Interface {
|
||||||
model: ModelV2.Ref
|
model: ModelV2.Ref
|
||||||
}) => Effect.Effect<void, NotFoundError>
|
}) => Effect.Effect<void, NotFoundError>
|
||||||
readonly rename: (input: { sessionID: SessionSchema.ID; title: string }) => Effect.Effect<void, NotFoundError>
|
readonly rename: (input: { sessionID: SessionSchema.ID; title: string }) => Effect.Effect<void, NotFoundError>
|
||||||
|
readonly archive: (sessionID: SessionSchema.ID) => Effect.Effect<void, NotFoundError>
|
||||||
readonly move: (input: {
|
readonly move: (input: {
|
||||||
sessionID: SessionSchema.ID
|
sessionID: SessionSchema.ID
|
||||||
directory: AbsolutePath
|
directory: AbsolutePath
|
||||||
|
|
@ -705,6 +706,11 @@ const layer = Layer.effect(
|
||||||
title: input.title,
|
title: input.title,
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
|
archive: Effect.fn("V2Session.archive")(function* (sessionID) {
|
||||||
|
const session = yield* result.get(sessionID)
|
||||||
|
if (session.time.archived) return
|
||||||
|
yield* events.publish(SessionEvent.Archived, { sessionID })
|
||||||
|
}),
|
||||||
move: Effect.fn("V2Session.move")(function* (input) {
|
move: Effect.fn("V2Session.move")(function* (input) {
|
||||||
const current = yield* result.get(input.sessionID)
|
const current = yield* result.get(input.sessionID)
|
||||||
const value = input.directory.trim()
|
const value = input.directory.trim()
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ export function fromRow(row: typeof SessionTable.$inferSelect): SessionSchema.In
|
||||||
time: {
|
time: {
|
||||||
created: DateTime.makeUnsafe(row.time_created),
|
created: DateTime.makeUnsafe(row.time_created),
|
||||||
updated: DateTime.makeUnsafe(row.time_updated),
|
updated: DateTime.makeUnsafe(row.time_updated),
|
||||||
archived: row.time_archived ? DateTime.makeUnsafe(row.time_archived) : undefined,
|
archived: row.time_archived !== null ? DateTime.makeUnsafe(row.time_archived) : undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -171,6 +171,7 @@ export function update(adapter: Adapter, event: SessionEvent.Event) {
|
||||||
},
|
},
|
||||||
"session.moved": () => Effect.void,
|
"session.moved": () => Effect.void,
|
||||||
"session.renamed": () => Effect.void,
|
"session.renamed": () => Effect.void,
|
||||||
|
"session.archived": () => Effect.void,
|
||||||
"session.deleted": () => Effect.void,
|
"session.deleted": () => Effect.void,
|
||||||
"session.forked": () => Effect.void,
|
"session.forked": () => Effect.void,
|
||||||
"session.input.promoted": () => Effect.void,
|
"session.input.promoted": () => Effect.void,
|
||||||
|
|
|
||||||
|
|
@ -609,6 +609,17 @@ const layer = Layer.effectDiscard(
|
||||||
.run()
|
.run()
|
||||||
.pipe(Effect.orDie),
|
.pipe(Effect.orDie),
|
||||||
)
|
)
|
||||||
|
yield* events.project(SessionEvent.Archived, (event) =>
|
||||||
|
db
|
||||||
|
.update(SessionTable)
|
||||||
|
.set({
|
||||||
|
time_archived: DateTime.toEpochMillis(event.created),
|
||||||
|
time_updated: DateTime.toEpochMillis(event.created),
|
||||||
|
})
|
||||||
|
.where(eq(SessionTable.id, event.data.sessionID))
|
||||||
|
.run()
|
||||||
|
.pipe(Effect.orDie),
|
||||||
|
)
|
||||||
yield* events.project(SessionEvent.UsageRecorded, (event) => applyUsage(db, event.data.sessionID, event.data))
|
yield* events.project(SessionEvent.UsageRecorded, (event) => applyUsage(db, event.data.sessionID, event.data))
|
||||||
yield* events.project(SessionEvent.Forked, (event) => projectFork(db, event))
|
yield* events.project(SessionEvent.Forked, (event) => projectFork(db, event))
|
||||||
yield* events.project(SessionEvent.InputPromoted, (event) =>
|
yield* events.project(SessionEvent.InputPromoted, (event) =>
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ const projects = Layer.succeed(
|
||||||
list: () => Effect.succeed([]),
|
list: () => Effect.succeed([]),
|
||||||
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
resolve: (directory) => Effect.succeed({ id: ProjectV2.ID.global, directory }),
|
||||||
directories: () => Effect.succeed([]),
|
directories: () => Effect.succeed([]),
|
||||||
|
update: () => Effect.die("unused"),
|
||||||
commit: () => Effect.void,
|
commit: () => Effect.void,
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
@ -660,4 +661,19 @@ describe("SessionV2.create", () => {
|
||||||
).toBe("Session.NotFoundError")
|
).toBe("Session.NotFoundError")
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
it.effect("archives a Session through one durable event", () =>
|
||||||
|
Effect.gen(function* () {
|
||||||
|
const session = yield* SessionV2.Service
|
||||||
|
const created = yield* session.create({ location })
|
||||||
|
|
||||||
|
yield* session.archive(created.id)
|
||||||
|
yield* session.archive(created.id)
|
||||||
|
|
||||||
|
expect((yield* session.get(created.id)).time.archived).toBeDefined()
|
||||||
|
const events = Array.from(yield* logEvents(session, created.id).pipe(Stream.runCollect))
|
||||||
|
expect(events.map((event) => event.type)).toContain("session.archived")
|
||||||
|
expect(events.filter((event) => event.type === "session.archived")).toHaveLength(1)
|
||||||
|
}),
|
||||||
|
)
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -269,6 +269,21 @@ export const makeSessionGroup = <I extends HttpApiMiddleware.AnyId, S>(sessionLo
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
.add(
|
||||||
|
HttpApiEndpoint.post("session.archive", "/api/session/:sessionID/archive", {
|
||||||
|
params: { sessionID: Session.ID },
|
||||||
|
success: HttpApiSchema.NoContent,
|
||||||
|
error: SessionNotFoundError,
|
||||||
|
})
|
||||||
|
.middleware(sessionLocationMiddleware)
|
||||||
|
.annotateMerge(
|
||||||
|
OpenApi.annotations({
|
||||||
|
identifier: "v2.session.archive",
|
||||||
|
summary: "Archive session",
|
||||||
|
description: "Archive a session.",
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
.add(
|
.add(
|
||||||
HttpApiEndpoint.post("session.move", "/api/session/:sessionID/move", {
|
HttpApiEndpoint.post("session.move", "/api/session/:sessionID/move", {
|
||||||
params: { sessionID: Session.ID },
|
params: { sessionID: Session.ID },
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,13 @@ export const Renamed = Event.durable({
|
||||||
})
|
})
|
||||||
export type Renamed = typeof Renamed.Type
|
export type Renamed = typeof Renamed.Type
|
||||||
|
|
||||||
|
export const Archived = Event.durable({
|
||||||
|
type: "session.archived",
|
||||||
|
...options,
|
||||||
|
schema: Base,
|
||||||
|
})
|
||||||
|
export type Archived = typeof Archived.Type
|
||||||
|
|
||||||
export const UsageRecorded = Event.durable({
|
export const UsageRecorded = Event.durable({
|
||||||
type: "session.usage.recorded",
|
type: "session.usage.recorded",
|
||||||
...options,
|
...options,
|
||||||
|
|
@ -539,6 +546,7 @@ export const Definitions = Event.inventory(
|
||||||
ModelSelected,
|
ModelSelected,
|
||||||
Moved,
|
Moved,
|
||||||
Renamed,
|
Renamed,
|
||||||
|
Archived,
|
||||||
UsageUpdated,
|
UsageUpdated,
|
||||||
Deleted,
|
Deleted,
|
||||||
Forked,
|
Forked,
|
||||||
|
|
|
||||||
|
|
@ -104,6 +104,7 @@ describe("public event manifest", () => {
|
||||||
"session.model.selected.1",
|
"session.model.selected.1",
|
||||||
"session.moved.1",
|
"session.moved.1",
|
||||||
"session.renamed.1",
|
"session.renamed.1",
|
||||||
|
"session.archived.1",
|
||||||
"session.usage.recorded.1",
|
"session.usage.recorded.1",
|
||||||
"session.forked.2",
|
"session.forked.2",
|
||||||
"session.input.promoted.1",
|
"session.input.promoted.1",
|
||||||
|
|
|
||||||
|
|
@ -201,6 +201,22 @@ export const SessionHandler = HttpApiBuilder.group(Api, "server.session", (handl
|
||||||
return HttpApiSchema.NoContent.make()
|
return HttpApiSchema.NoContent.make()
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.handle(
|
||||||
|
"session.archive",
|
||||||
|
Effect.fn(function* (ctx) {
|
||||||
|
yield* session.archive(ctx.params.sessionID).pipe(
|
||||||
|
Effect.catchTag("Session.NotFoundError", (error) =>
|
||||||
|
Effect.fail(
|
||||||
|
new SessionNotFoundError({
|
||||||
|
sessionID: error.sessionID,
|
||||||
|
message: `Session not found: ${error.sessionID}`,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
return HttpApiSchema.NoContent.make()
|
||||||
|
}),
|
||||||
|
)
|
||||||
.handle(
|
.handle(
|
||||||
"session.move",
|
"session.move",
|
||||||
Effect.fn(function* (ctx) {
|
Effect.fn(function* (ctx) {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue