reference.ts 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. export * as Reference from "./reference"
  2. import { Context, Effect, Layer, Schema, Scope } from "effect"
  3. import { castDraft } from "immer"
  4. import { Global } from "./global"
  5. import { EventV2 } from "./event"
  6. import { Repository } from "./repository"
  7. import { RepositoryCache } from "./repository-cache"
  8. import { AbsolutePath } from "./schema"
  9. import { State } from "./state"
  10. export class Info extends Schema.Class<Info>("Reference.Info")({
  11. name: Schema.String,
  12. path: AbsolutePath,
  13. source: Schema.suspend(() => Source),
  14. }) {}
  15. export class LocalSource extends Schema.Class<LocalSource>("Reference.LocalSource")({
  16. type: Schema.Literal("local"),
  17. path: AbsolutePath,
  18. }) {}
  19. export class GitSource extends Schema.Class<GitSource>("Reference.GitSource")({
  20. type: Schema.Literal("git"),
  21. repository: Schema.String,
  22. branch: Schema.String.pipe(Schema.optional),
  23. }) {}
  24. export const Source = Schema.Union([LocalSource, GitSource]).pipe(Schema.toTaggedUnion("type"))
  25. export type Source = typeof Source.Type
  26. export const Event = {
  27. Updated: EventV2.define({ type: "reference.updated", schema: {} }),
  28. }
  29. type Data = {
  30. sources: Map<string, Source>
  31. }
  32. type Editor = {
  33. add(name: string, source: Source): void
  34. remove(name: string): void
  35. list(): readonly [string, Source][]
  36. }
  37. export interface Interface {
  38. readonly transform: State.Interface<Data, Editor>["transform"]
  39. readonly list: () => Effect.Effect<Info[]>
  40. }
  41. export class Service extends Context.Service<Service, Interface>()("@opencode/v2/Reference") {}
  42. export const layer = Layer.effect(
  43. Service,
  44. Effect.gen(function* () {
  45. const global = yield* Global.Service
  46. const events = yield* EventV2.Service
  47. const cache = yield* RepositoryCache.Service
  48. const scope = yield* Scope.Scope
  49. const materialized = new Map<string, Info>()
  50. const state = State.create<Data, Editor>({
  51. initial: () => ({ sources: new Map() }),
  52. editor: (draft) => ({
  53. add: (name, source) => draft.sources.set(name, castDraft(source)),
  54. remove: (name) => draft.sources.delete(name),
  55. list: () => Array.from(draft.sources.entries()) as [string, Source][],
  56. }),
  57. finalize: (editor) =>
  58. Effect.gen(function* () {
  59. materialized.clear()
  60. const seen = new Map<string, string | undefined>()
  61. for (const [name, source] of editor.list()) {
  62. if (source.type === "local") {
  63. materialized.set(name, new Info({ name, path: source.path, source }))
  64. continue
  65. }
  66. const repository = Repository.parse(source.repository)
  67. if (!repository || !Repository.isRemote(repository)) continue
  68. if (source.branch) {
  69. try {
  70. Repository.validateBranch(source.branch)
  71. } catch {
  72. continue
  73. }
  74. }
  75. const target = Repository.cachePath(global.repos, repository)
  76. if (seen.has(target) && seen.get(target) !== source.branch) continue
  77. seen.set(target, source.branch)
  78. materialized.set(name, new Info({ name, path: AbsolutePath.make(target), source }))
  79. yield* cache.ensure({ reference: repository, branch: source.branch, refresh: true }).pipe(
  80. Effect.catchCause((cause) =>
  81. Effect.logWarning("failed to materialize reference", {
  82. name,
  83. repository: source.repository,
  84. cause,
  85. }),
  86. ),
  87. Effect.forkIn(scope),
  88. )
  89. }
  90. yield* events.publish(Event.Updated, {})
  91. }),
  92. })
  93. return Service.of({
  94. transform: state.transform,
  95. list: Effect.fn("Reference.list")(function* () {
  96. return Array.from(materialized.values())
  97. }),
  98. })
  99. }),
  100. )
  101. export const locationLayer = layer