refactor(sync): make session events schema-first (#24019)
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream } from "effect"
|
import { Effect, Exit, Layer, PubSub, Scope, Context, Stream, Schema as EffectSchema, Types } from "effect"
|
||||||
import { EffectBridge } from "@/effect"
|
import { EffectBridge } from "@/effect"
|
||||||
import { Log } from "../util"
|
import { Log } from "../util"
|
||||||
import { BusEvent } from "./bus-event"
|
import { BusEvent } from "./bus-event"
|
||||||
@@ -9,6 +9,12 @@ import { makeRuntime } from "@/effect/run-service"
|
|||||||
|
|
||||||
const log = Log.create({ service: "bus" })
|
const log = Log.create({ service: "bus" })
|
||||||
|
|
||||||
|
type BusProperties<D extends BusEvent.Definition = BusEvent.Definition> = D extends {
|
||||||
|
effectProperties: infer Properties extends EffectSchema.Top
|
||||||
|
}
|
||||||
|
? Types.DeepMutable<EffectSchema.Schema.Type<Properties>>
|
||||||
|
: z.infer<D["properties"]>
|
||||||
|
|
||||||
export const InstanceDisposed = BusEvent.define(
|
export const InstanceDisposed = BusEvent.define(
|
||||||
"server.instance.disposed",
|
"server.instance.disposed",
|
||||||
z.object({
|
z.object({
|
||||||
@@ -18,7 +24,7 @@ export const InstanceDisposed = BusEvent.define(
|
|||||||
|
|
||||||
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
|
type Payload<D extends BusEvent.Definition = BusEvent.Definition> = {
|
||||||
type: D["type"]
|
type: D["type"]
|
||||||
properties: z.infer<D["properties"]>
|
properties: BusProperties<D>
|
||||||
}
|
}
|
||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
@@ -29,7 +35,7 @@ type State = {
|
|||||||
export interface Interface {
|
export interface Interface {
|
||||||
readonly publish: <D extends BusEvent.Definition>(
|
readonly publish: <D extends BusEvent.Definition>(
|
||||||
def: D,
|
def: D,
|
||||||
properties: z.output<D["properties"]>,
|
properties: BusProperties<D>,
|
||||||
) => Effect.Effect<void>
|
) => Effect.Effect<void>
|
||||||
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
|
readonly subscribe: <D extends BusEvent.Definition>(def: D) => Stream.Stream<Payload<D>>
|
||||||
readonly subscribeAll: () => Stream.Stream<Payload>
|
readonly subscribeAll: () => Stream.Stream<Payload>
|
||||||
@@ -79,7 +85,10 @@ export const layer = Layer.effect(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
function publish<D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
properties: BusProperties<D>,
|
||||||
|
) {
|
||||||
return Effect.gen(function* () {
|
return Effect.gen(function* () {
|
||||||
const s = yield* InstanceState.get(state)
|
const s = yield* InstanceState.get(state)
|
||||||
const payload: Payload = { type: def.type, properties }
|
const payload: Payload = { type: def.type, properties }
|
||||||
@@ -175,13 +184,16 @@ const { runPromise, runSync } = makeRuntime(Service, layer)
|
|||||||
|
|
||||||
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
|
// runSync is safe here because the subscribe chain (InstanceState.get, PubSub.subscribe,
|
||||||
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
|
// Scope.make, Effect.forkScoped) is entirely synchronous. If any step becomes async, this will throw.
|
||||||
export async function publish<D extends BusEvent.Definition>(def: D, properties: z.output<D["properties"]>) {
|
export async function publish<D extends BusEvent.Definition>(
|
||||||
|
def: D,
|
||||||
|
properties: BusProperties<D>,
|
||||||
|
) {
|
||||||
return runPromise((svc) => svc.publish(def, properties))
|
return runPromise((svc) => svc.publish(def, properties))
|
||||||
}
|
}
|
||||||
|
|
||||||
export function subscribe<D extends BusEvent.Definition>(
|
export function subscribe<D extends BusEvent.Definition>(
|
||||||
def: D,
|
def: D,
|
||||||
callback: (event: { type: D["type"]; properties: z.infer<D["properties"]> }) => unknown,
|
callback: (event: Payload<D>) => unknown,
|
||||||
) {
|
) {
|
||||||
return runSync((svc) => svc.subscribeCallback(def, callback))
|
return runSync((svc) => svc.subscribeCallback(def, callback))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import z from "zod"
|
|
||||||
import sessionProjectors from "../session/projectors"
|
import sessionProjectors from "../session/projectors"
|
||||||
import { SyncEvent } from "@/sync"
|
import { SyncEvent } from "@/sync"
|
||||||
import { Session } from "@/session"
|
import { Session } from "@/session"
|
||||||
@@ -10,7 +9,7 @@ export function initProjectors() {
|
|||||||
projectors: sessionProjectors,
|
projectors: sessionProjectors,
|
||||||
convertEvent: (type, data) => {
|
convertEvent: (type, data) => {
|
||||||
if (type === "session.updated") {
|
if (type === "session.updated") {
|
||||||
const id = (data as z.infer<typeof Session.Event.Updated.schema>).sessionID
|
const id = (data as SyncEvent.Event<typeof Session.Event.Updated>["data"]).sessionID
|
||||||
const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
|
const row = Database.use((db) => db.select().from(SessionTable).where(eq(SessionTable.id, id)).get())
|
||||||
|
|
||||||
if (!row) return data
|
if (!row) return data
|
||||||
|
|||||||
@@ -576,34 +576,46 @@ export const Info = Object.assign(_Info, {
|
|||||||
})
|
})
|
||||||
export type Info = User | Assistant
|
export type Info = User | Assistant
|
||||||
|
|
||||||
|
const UpdatedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
info: _Info,
|
||||||
|
})
|
||||||
|
|
||||||
|
const RemovedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
messageID: MessageID,
|
||||||
|
})
|
||||||
|
|
||||||
|
const PartUpdatedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
part: _Part,
|
||||||
|
time: Schema.Number,
|
||||||
|
})
|
||||||
|
|
||||||
|
const PartRemovedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
messageID: MessageID,
|
||||||
|
partID: PartID,
|
||||||
|
})
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Updated: SyncEvent.define({
|
Updated: SyncEvent.define({
|
||||||
type: "message.updated",
|
type: "message.updated",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: UpdatedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
info: Info.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
Removed: SyncEvent.define({
|
Removed: SyncEvent.define({
|
||||||
type: "message.removed",
|
type: "message.removed",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: RemovedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
messageID: MessageID.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
PartUpdated: SyncEvent.define({
|
PartUpdated: SyncEvent.define({
|
||||||
type: "message.part.updated",
|
type: "message.part.updated",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: PartUpdatedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
part: Part.zod,
|
|
||||||
time: z.number(),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
PartDelta: BusEvent.define(
|
PartDelta: BusEvent.define(
|
||||||
"message.part.delta",
|
"message.part.delta",
|
||||||
@@ -619,11 +631,7 @@ export const Event = {
|
|||||||
type: "message.part.removed",
|
type: "message.part.removed",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: PartRemovedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
messageID: MessageID.zod,
|
|
||||||
partID: PartID.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ export default [
|
|||||||
const info = data.info
|
const info = data.info
|
||||||
const row = db
|
const row = db
|
||||||
.update(SessionTable)
|
.update(SessionTable)
|
||||||
.set(toPartialRow(info))
|
.set(toPartialRow(info as Session.Patch))
|
||||||
.where(eq(SessionTable.id, data.sessionID))
|
.where(eq(SessionTable.id, data.sessionID))
|
||||||
.returning()
|
.returning()
|
||||||
.get()
|
.get()
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { PartTable, SessionTable } from "./session.sql"
|
|||||||
import { ProjectTable } from "../project/project.sql"
|
import { ProjectTable } from "../project/project.sql"
|
||||||
import { Storage } from "@/storage"
|
import { Storage } from "@/storage"
|
||||||
import { Log } from "../util"
|
import { Log } from "../util"
|
||||||
import { updateSchema } from "../util/update-schema"
|
|
||||||
import { MessageV2 } from "./message-v2"
|
import { MessageV2 } from "./message-v2"
|
||||||
import { Instance } from "../project/instance"
|
import { Instance } from "../project/instance"
|
||||||
import { InstanceState } from "@/effect"
|
import { InstanceState } from "@/effect"
|
||||||
@@ -28,7 +27,7 @@ import type { Provider } from "@/provider"
|
|||||||
import { Permission } from "@/permission"
|
import { Permission } from "@/permission"
|
||||||
import { Global } from "@/global"
|
import { Global } from "@/global"
|
||||||
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
|
import { Effect, Layer, Option, Context, Schema, Types } from "effect"
|
||||||
import { zod, zodObject } from "@/util/effect-zod"
|
import { zod } from "@/util/effect-zod"
|
||||||
import { withStatics } from "@/util/schema"
|
import { withStatics } from "@/util/schema"
|
||||||
|
|
||||||
const log = Log.create({ service: "session" })
|
const log = Log.create({ service: "session" })
|
||||||
@@ -215,40 +214,62 @@ export const MessagesInput = Schema.Struct({
|
|||||||
limit: Schema.optional(Schema.Number),
|
limit: Schema.optional(Schema.Number),
|
||||||
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
}).pipe(withStatics((s) => ({ zod: zod(s) })))
|
||||||
|
|
||||||
|
const CreatedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
info: Info,
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdatedShare = Schema.Struct({
|
||||||
|
url: Schema.optional(Schema.NullOr(Schema.String)),
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdatedTime = Schema.Struct({
|
||||||
|
created: Schema.optional(Schema.NullOr(Schema.Number)),
|
||||||
|
updated: Schema.optional(Schema.NullOr(Schema.Number)),
|
||||||
|
compacting: Schema.optional(Schema.NullOr(Schema.Number)),
|
||||||
|
archived: Schema.optional(Schema.NullOr(Schema.Number)),
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdatedInfo = Schema.Struct({
|
||||||
|
id: Schema.optional(Schema.NullOr(SessionID)),
|
||||||
|
slug: Schema.optional(Schema.NullOr(Schema.String)),
|
||||||
|
projectID: Schema.optional(Schema.NullOr(ProjectID)),
|
||||||
|
workspaceID: Schema.optional(Schema.NullOr(WorkspaceID)),
|
||||||
|
directory: Schema.optional(Schema.NullOr(Schema.String)),
|
||||||
|
parentID: Schema.optional(Schema.NullOr(SessionID)),
|
||||||
|
summary: Schema.optional(Schema.NullOr(Summary)),
|
||||||
|
share: Schema.optional(UpdatedShare),
|
||||||
|
title: Schema.optional(Schema.NullOr(Schema.String)),
|
||||||
|
version: Schema.optional(Schema.NullOr(Schema.String)),
|
||||||
|
time: Schema.optional(UpdatedTime),
|
||||||
|
permission: Schema.optional(Schema.NullOr(Permission.Ruleset)),
|
||||||
|
revert: Schema.optional(Schema.NullOr(Revert)),
|
||||||
|
})
|
||||||
|
|
||||||
|
const UpdatedEventSchema = Schema.Struct({
|
||||||
|
sessionID: SessionID,
|
||||||
|
info: UpdatedInfo,
|
||||||
|
})
|
||||||
|
|
||||||
export const Event = {
|
export const Event = {
|
||||||
Created: SyncEvent.define({
|
Created: SyncEvent.define({
|
||||||
type: "session.created",
|
type: "session.created",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: CreatedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
info: Info.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
Updated: SyncEvent.define({
|
Updated: SyncEvent.define({
|
||||||
type: "session.updated",
|
type: "session.updated",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: UpdatedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
busSchema: CreatedEventSchema,
|
||||||
info: updateSchema(zodObject(Info)).extend({
|
|
||||||
share: updateSchema(zodObject(Share)).optional(),
|
|
||||||
time: updateSchema(zodObject(Time)).optional(),
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
busSchema: z.object({
|
|
||||||
sessionID: SessionID.zod,
|
|
||||||
info: Info.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
Deleted: SyncEvent.define({
|
Deleted: SyncEvent.define({
|
||||||
type: "session.deleted",
|
type: "session.deleted",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "sessionID",
|
aggregate: "sessionID",
|
||||||
schema: z.object({
|
schema: CreatedEventSchema,
|
||||||
sessionID: SessionID.zod,
|
|
||||||
info: Info.zod,
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
Diff: BusEvent.define(
|
Diff: BusEvent.define(
|
||||||
"session.diff",
|
"session.diff",
|
||||||
@@ -394,7 +415,7 @@ export interface Interface {
|
|||||||
|
|
||||||
export class Service extends Context.Service<Service, Interface>()("@opencode/Session") {}
|
export class Service extends Context.Service<Service, Interface>()("@opencode/Session") {}
|
||||||
|
|
||||||
type Patch = z.infer<typeof Event.Updated.schema>["info"]
|
export type Patch = Types.DeepMutable<SyncEvent.Event<typeof Event.Updated>["data"]["info"]>
|
||||||
|
|
||||||
const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
|
const db = <T>(fn: (d: Parameters<typeof Database.use>[0] extends (trx: infer D) => any ? D : never) => T) =>
|
||||||
Effect.sync(() => Database.use(fn))
|
Effect.sync(() => Database.use(fn))
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export const layer = Layer.effect(
|
|||||||
|
|
||||||
yield* watch(Session.Event.Updated, (evt) =>
|
yield* watch(Session.Event.Updated, (evt) =>
|
||||||
Effect.gen(function* () {
|
Effect.gen(function* () {
|
||||||
const info = yield* session.get(evt.properties.sessionID)
|
const info = evt.properties.info
|
||||||
yield* sync(info.id, [{ type: "session", data: info }])
|
yield* sync(info.id, [{ type: "session", data: info }])
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import z from "zod"
|
import z from "zod"
|
||||||
import type { ZodObject } from "zod"
|
|
||||||
import { Database, eq } from "@/storage"
|
import { Database, eq } from "@/storage"
|
||||||
import { GlobalBus } from "@/bus/global"
|
import { GlobalBus } from "@/bus/global"
|
||||||
import { Bus as ProjectBus } from "@/bus"
|
import { Bus as ProjectBus } from "@/bus"
|
||||||
@@ -9,11 +8,16 @@ import { EventSequenceTable, EventTable } from "./event.sql"
|
|||||||
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
import { WorkspaceContext } from "@/control-plane/workspace-context"
|
||||||
import { EventID } from "./schema"
|
import { EventID } from "./schema"
|
||||||
import { Flag } from "@/flag/flag"
|
import { Flag } from "@/flag/flag"
|
||||||
|
import { Schema as EffectSchema, Types } from "effect"
|
||||||
|
import { zodObject } from "@/util/effect-zod"
|
||||||
|
import { isRecord } from "@/util/record"
|
||||||
|
|
||||||
export type Definition = {
|
export type Definition<Schema extends EffectSchema.Top = EffectSchema.Top, BusSchema extends EffectSchema.Top = Schema> = {
|
||||||
type: string
|
type: string
|
||||||
version: number
|
version: number
|
||||||
aggregate: string
|
aggregate: string
|
||||||
|
effectSchema: Schema
|
||||||
|
effectProperties: BusSchema
|
||||||
schema: z.ZodObject
|
schema: z.ZodObject
|
||||||
|
|
||||||
// This is temporary and only exists for compatibility with bus
|
// This is temporary and only exists for compatibility with bus
|
||||||
@@ -25,9 +29,13 @@ export type Event<Def extends Definition = Definition> = {
|
|||||||
id: string
|
id: string
|
||||||
seq: number
|
seq: number
|
||||||
aggregateID: string
|
aggregateID: string
|
||||||
data: z.infer<Def["schema"]>
|
data: Types.DeepMutable<EffectSchema.Schema.Type<Def["effectSchema"]>>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Properties<Def extends Definition = Definition> = Types.DeepMutable<
|
||||||
|
EffectSchema.Schema.Type<Def["effectProperties"]>
|
||||||
|
>
|
||||||
|
|
||||||
export type SerializedEvent<Def extends Definition = Definition> = Event<Def> & { type: string }
|
export type SerializedEvent<Def extends Definition = Definition> = Event<Def> & { type: string }
|
||||||
|
|
||||||
type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void
|
type ProjectorFunc = (db: Database.TxOrDb, data: unknown) => void
|
||||||
@@ -36,7 +44,12 @@ export const registry = new Map<string, Definition>()
|
|||||||
let projectors: Map<Definition, ProjectorFunc> | undefined
|
let projectors: Map<Definition, ProjectorFunc> | undefined
|
||||||
const versions = new Map<string, number>()
|
const versions = new Map<string, number>()
|
||||||
let frozen = false
|
let frozen = false
|
||||||
let convertEvent: (type: string, event: Event["data"]) => Promise<Record<string, unknown>> | Record<string, unknown>
|
let convertEvent: (type: string, event: Event["data"]) => Promise<unknown> | unknown
|
||||||
|
|
||||||
|
function asRecord(input: unknown) {
|
||||||
|
if (isRecord(input)) return input
|
||||||
|
throw new Error(`SyncEvent.convertEvent must return an object, got: ${JSON.stringify(input)}`)
|
||||||
|
}
|
||||||
|
|
||||||
export function reset() {
|
export function reset() {
|
||||||
frozen = false
|
frozen = false
|
||||||
@@ -54,7 +67,7 @@ export function init(input: { projectors: Array<[Definition, ProjectorFunc]>; co
|
|||||||
for (let [type, version] of versions.entries()) {
|
for (let [type, version] of versions.entries()) {
|
||||||
let def = registry.get(versionedType(type, version))!
|
let def = registry.get(versionedType(type, version))!
|
||||||
|
|
||||||
BusEvent.define(def.type, def.properties || def.schema)
|
BusEvent.define(def.type, def.properties)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Freeze the system so it clearly errors if events are defined
|
// Freeze the system so it clearly errors if events are defined
|
||||||
@@ -72,19 +85,26 @@ export function versionedType(type: string, version?: number) {
|
|||||||
export function define<
|
export function define<
|
||||||
Type extends string,
|
Type extends string,
|
||||||
Agg extends string,
|
Agg extends string,
|
||||||
Schema extends ZodObject<Record<Agg, z.ZodType<string>>>,
|
Schema extends EffectSchema.Top,
|
||||||
BusSchema extends ZodObject = Schema,
|
BusSchema extends EffectSchema.Top = Schema,
|
||||||
>(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }) {
|
>(input: { type: Type; version: number; aggregate: Agg; schema: Schema; busSchema?: BusSchema }): Definition<
|
||||||
|
Schema,
|
||||||
|
BusSchema
|
||||||
|
> {
|
||||||
if (frozen) {
|
if (frozen) {
|
||||||
throw new Error("Error defining sync event: sync system has been frozen")
|
throw new Error("Error defining sync event: sync system has been frozen")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const effectProperties = (input.busSchema ?? input.schema) as BusSchema
|
||||||
|
|
||||||
const def = {
|
const def = {
|
||||||
type: input.type,
|
type: input.type,
|
||||||
version: input.version,
|
version: input.version,
|
||||||
aggregate: input.aggregate,
|
aggregate: input.aggregate,
|
||||||
schema: input.schema,
|
effectSchema: input.schema,
|
||||||
properties: input.busSchema ? input.busSchema : input.schema,
|
effectProperties,
|
||||||
|
schema: zodObject(input.schema),
|
||||||
|
properties: zodObject(effectProperties),
|
||||||
}
|
}
|
||||||
|
|
||||||
versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0))
|
versions.set(def.type, Math.max(def.version, versions.get(def.type) || 0))
|
||||||
@@ -143,10 +163,10 @@ function process<Def extends Definition>(def: Def, event: Event<Def>, options: {
|
|||||||
const result = convertEvent(def.type, event.data)
|
const result = convertEvent(def.type, event.data)
|
||||||
if (result instanceof Promise) {
|
if (result instanceof Promise) {
|
||||||
void result.then((data) => {
|
void result.then((data) => {
|
||||||
void ProjectBus.publish({ type: def.type, properties: def.schema }, data)
|
void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(data))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
void ProjectBus.publish({ type: def.type, properties: def.schema }, result)
|
void ProjectBus.publish({ type: def.type, properties: def.properties }, asRecord(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalBus.emit("event", {
|
GlobalBus.emit("event", {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test"
|
import { describe, test, expect, beforeEach, afterEach, afterAll } from "bun:test"
|
||||||
import { tmpdir } from "../fixture/fixture"
|
import { tmpdir } from "../fixture/fixture"
|
||||||
import z from "zod"
|
import { Schema } from "effect"
|
||||||
import { Bus } from "../../src/bus"
|
import { Bus } from "../../src/bus"
|
||||||
import { Instance } from "../../src/project/instance"
|
import { Instance } from "../../src/project/instance"
|
||||||
import { SyncEvent } from "../../src/sync"
|
import { SyncEvent } from "../../src/sync"
|
||||||
@@ -43,13 +43,13 @@ describe("SyncEvent", () => {
|
|||||||
type: "item.created",
|
type: "item.created",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "id",
|
aggregate: "id",
|
||||||
schema: z.object({ id: z.string(), name: z.string() }),
|
schema: Schema.Struct({ id: Schema.String, name: Schema.String }),
|
||||||
})
|
})
|
||||||
const Sent = SyncEvent.define({
|
const Sent = SyncEvent.define({
|
||||||
type: "item.sent",
|
type: "item.sent",
|
||||||
version: 1,
|
version: 1,
|
||||||
aggregate: "item_id",
|
aggregate: "item_id",
|
||||||
schema: z.object({ item_id: z.string(), to: z.string() }),
|
schema: Schema.Struct({ item_id: Schema.String, to: Schema.String }),
|
||||||
})
|
})
|
||||||
|
|
||||||
SyncEvent.init({
|
SyncEvent.init({
|
||||||
|
|||||||
@@ -1058,31 +1058,31 @@ export type SyncEventSessionUpdated = {
|
|||||||
data: {
|
data: {
|
||||||
sessionID: string
|
sessionID: string
|
||||||
info: {
|
info: {
|
||||||
id: string | null
|
id?: string | null
|
||||||
slug: string | null
|
slug?: string | null
|
||||||
projectID: string | null
|
projectID?: string | null
|
||||||
workspaceID: string | null
|
workspaceID?: string | null
|
||||||
directory: string | null
|
directory?: string | null
|
||||||
parentID: string | null
|
parentID?: string | null
|
||||||
summary: {
|
summary?: {
|
||||||
additions: number
|
additions: number
|
||||||
deletions: number
|
deletions: number
|
||||||
files: number
|
files: number
|
||||||
diffs?: Array<SnapshotFileDiff>
|
diffs?: Array<SnapshotFileDiff>
|
||||||
} | null
|
} | null
|
||||||
share?: {
|
share?: {
|
||||||
url: string | null
|
url?: string | null
|
||||||
}
|
}
|
||||||
title: string | null
|
title?: string | null
|
||||||
version: string | null
|
version?: string | null
|
||||||
time?: {
|
time?: {
|
||||||
created: number | null
|
created?: number | null
|
||||||
updated: number | null
|
updated?: number | null
|
||||||
compacting: number | null
|
compacting?: number | null
|
||||||
archived: number | null
|
archived?: number | null
|
||||||
}
|
}
|
||||||
permission: PermissionRuleset | null
|
permission?: PermissionRuleset | null
|
||||||
revert: {
|
revert?: {
|
||||||
messageID: string
|
messageID: string
|
||||||
partID?: string
|
partID?: string
|
||||||
snapshot?: string
|
snapshot?: string
|
||||||
|
|||||||
Reference in New Issue
Block a user