reference.ts 4.1 KB

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