{
  "version": 3,
  "sources": ["../../../src/Feed.ts"],
  "sourcesContent": ["//\n// Copyright 2025 DXOS.org\n//\n\n// @import-as-namespace\n\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport type * as Option from 'effect/Option';\nimport * as Schema from 'effect/Schema';\n\nimport { DXN, EID } from '@dxos/keys';\n\nimport * as Annotation from './Annotation';\nimport type * as Entity from './Entity';\nimport type * as Filter from './Filter';\nimport * as internal from './internal';\nimport * as Obj from './Obj';\nimport type * as Query from './Query';\nimport type * as QueryResult from './QueryResult';\nimport * as Type from './Type';\n\n/**\n * Runtime schema for a Feed object.\n *\n * @example\n * ```ts\n * const feed = Obj.make(Feed.Feed, { name: 'notifications', kind: 'org.dxos.plugin.notifications.v1' });\n * ```\n */\nexport const Feed = Schema.Struct({\n  /** User-facing display name. */\n  name: Schema.String.pipe(Schema.optional),\n  /** Identifier for the feed's kind (e.g., plugin id). */\n  kind: Schema.String.pipe(internal.FormInputAnnotation.set(false), Schema.optional),\n\n  /**\n   * Feed namespace.\n   * Controls how feed data is stored and replicated.\n   * - `data`: Data feed (default).\n   * - `trace`: Trace feed.\n   */\n  namespace: Schema.optional(Schema.Literal('data', 'trace')),\n}).pipe(\n  internal.HiddenAnnotation.set(true),\n  Annotation.IconAnnotation.set({ icon: 'ph--rows--regular', hue: 'yellow' }),\n  Type.makeObject(DXN.make('org.dxos.type.feed', '0.1.0')),\n);\n\n/**\n * TypeScript instance type for a Feed object.\n */\nexport type Feed = Type.InstanceType<typeof Feed>;\n\n//\n// Types\n//\n\n/**\n * Opaque cursor for iterating over feed items.\n */\n// TODO(dmaretskyi): T needs to be referenced in the type structure for typescript to respect it during inference and type-checking.\nexport interface Cursor<T = Obj.Snapshot> {\n  readonly _tag: 'Cursor';\n}\n\n/**\n * Retention options for a feed.\n */\nexport interface RetentionOptions {\n  /** Retain items after this cursor position. */\n  // TODO(wittjosiah): Use FeedCursor from @dxos/feed?\n  cursor?: string;\n}\n\n/**\n * Sync options for a feed.\n */\nexport interface SyncOptions {\n  /** Push local changes to the server. Defaults to true. */\n  shouldPush?: boolean;\n  /** Pull remote changes from the server. Defaults to true. */\n  shouldPull?: boolean;\n}\n\n/**\n * Queue replication backlog for a feed namespace.\n * `0` / `0` means caught up on pull and push.\n */\nexport interface SyncState {\n  /** Blocks still to pull from remote. */\n  blocksToPull: number;\n  /** Unpositioned blocks still to push to remote. */\n  blocksToPush: number;\n  /** Total blocks stored locally for the feed namespace. */\n  totalBlocks: number;\n}\n\n//\n// Factory\n//\n\n/**\n * Creates a new feed object.\n *\n * @example\n * ```ts\n * const feed = Feed.make({ name: 'notifications', kind: 'org.dxos.plugin.notifications.v1' });\n * ```\n */\n// TODO(wittjosiah): How to control the feed namespace (data/trace)? Why do feeds have namespaces?\nexport const make = (props: Obj.MakeProps<typeof Feed> = {}): Feed => Obj.make(Feed, props);\n\n/**\n * Returns the feed object's EID when the feed is stored in a space.\n *\n * Used internally by the feed service layer.\n */\nexport const getQueueUri = (feed: Feed): EID.EID | undefined => EID.tryParse(Obj.getURI(feed));\n\n//\n// Service\n//\n\n/**\n * Effect service for feed operations.\n * Provides the bridge to the underlying storage implementation.\n * Must be provided by the application layer (e.g., echo-db).\n */\nexport class FeedService extends Context.Tag('@dxos/echo/Feed/FeedService')<\n  FeedService,\n  {\n    /**\n     * Appends items to a feed.\n     */\n    append(feed: Feed, items: Entity.Unknown[]): Promise<void>;\n\n    /**\n     * Removes items from a feed by ID.\n     */\n    remove(feed: Feed, ids: string[]): Promise<void>;\n\n    /**\n     * Queries items in a feed.\n     */\n    query: {\n      <Q extends Query.Any>(feed: Feed, query: Q): QueryResult.QueryResult<Query.Type<Q>>;\n      <F extends Filter.Any>(feed: Feed, filter: F): QueryResult.QueryResult<Filter.Type<F>>;\n    };\n\n    /**\n     * Syncs the feed with the server.\n     */\n    sync(feed: Feed, options?: SyncOptions): Promise<void>;\n\n    /**\n     * Returns queue replication backlog for the feed's namespace.\n     */\n    getSyncState(feed: Feed): Promise<SyncState>;\n  }\n>() {}\n\n/**\n * @deprecated Use `FeedService` instead.\n */\nexport type Service = FeedService;\n\n/**\n * @deprecated Use `FeedService` instead.\n */\nexport const Service = FeedService;\n\n/**\n * Effect context service that holds the current feed for a scoped operation.\n *\n * @deprecated Prefer threading a `Feed.Feed` explicitly through function signatures\n * over hiding it behind a context service.\n */\nexport class ContextFeedService extends Context.Tag('@dxos/echo/Feed/ContextFeedService')<\n  ContextFeedService,\n  {\n    readonly feed: Feed;\n  }\n>() {\n  static layer = (feed: Feed): Layer.Layer<ContextFeedService> => Layer.succeed(ContextFeedService, { feed });\n}\n\n/**\n * Layer that provides a Feed service that throws when accessed.\n * Useful as a default layer when no feed service is available.\n */\nexport const notAvailable: Layer.Layer<FeedService> = Layer.succeed(FeedService, {\n  append: () => {\n    throw new Error('Feed.FeedService not available');\n  },\n  remove: () => {\n    throw new Error('Feed.FeedService not available');\n  },\n  query: () => {\n    throw new Error('Feed.FeedService not available');\n  },\n  sync: () => {\n    throw new Error('Feed.FeedService not available');\n  },\n  getSyncState: () => {\n    throw new Error('Feed.FeedService not available');\n  },\n} as Context.Tag.Service<FeedService>);\n\n//\n// Operations\n//\n\n/**\n * Appends items to a feed.\n *\n * @example\n * ```ts\n * yield* Feed.append(feed, [Obj.make(Notification, { title: 'Hello' })]);\n * ```\n */\nexport const append = (feed: Feed, items: Entity.Unknown[]): Effect.Effect<void, never, FeedService> =>\n  Effect.gen(function* () {\n    const service = yield* FeedService;\n    yield* Effect.promise(() => service.append(feed, items));\n  });\n\n/**\n * Removes items from a feed.\n *\n * @example\n * ```ts\n * yield* Feed.remove(feed, [item]);\n * ```\n */\n// TODO(dmaretskyi): Should we allow snapshots here? - what does it mean to remove a snapshot?\nexport const remove = (feed: Feed, items: (Entity.Unknown | Obj.Snapshot)[]): Effect.Effect<void, never, FeedService> =>\n  Effect.gen(function* () {\n    const service = yield* FeedService;\n    const ids = items.map((item) => item.id);\n    yield* Effect.promise(() => service.remove(feed, ids));\n  });\n\n/**\n * Creates a reactive query over items in a feed.\n *\n * @example\n * ```ts\n * const result = yield* Feed.query(feed, Filter.type(Person));\n * ```\n */\n// TODO(dmaretskyi): Suport chained queries:\n//                   const result = yield* feed.pipe(Feed.query(Filter.type(Person))); result.subscribe(...)\n//                   const objects = yield* feed.pipe(Feed.query(Filter.type(Person))).run;\n//                   const object = yield* feed.pipe(Feed.query(Filter.type(Person))).first;\n// ... unify for Database and schema queries.\nexport const query: {\n  <Q extends Query.Any>(\n    feed: Feed,\n    query: Q,\n  ): Effect.Effect<QueryResult.QueryResult<Query.Type<Q>>, never, FeedService>;\n  <F extends Filter.Any>(\n    feed: Feed,\n    filter: F,\n  ): Effect.Effect<QueryResult.QueryResult<Filter.Type<F>>, never, FeedService>;\n} = (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>\n  FeedService.pipe(Effect.map((service) => service.query(feed, queryOrFilter as any) as QueryResult.QueryResult<any>));\n\n/**\n * Executes a feed query once and returns the results.\n *\n * @example\n * ```ts\n * const items = yield* Feed.runQuery(feed, Filter.type(Person));\n * ```\n */\nexport const runQuery: {\n  <Q extends Query.Any>(feed: Feed, query: Q): Effect.Effect<Query.Type<Q>[], never, FeedService>;\n  <F extends Filter.Any>(feed: Feed, filter: F): Effect.Effect<Filter.Type<F>[], never, FeedService>;\n} = (feed: Feed, queryOrFilter: Query.Any | Filter.Any) =>\n  query(feed, queryOrFilter as any).pipe(Effect.flatMap((queryResult) => Effect.promise(() => queryResult.run())));\n\n/**\n * Syncs the feed with the server.\n *\n * @example\n * ```ts\n * yield* Feed.sync(feed);\n * yield* Feed.sync(feed, { shouldPush: false });\n * ```\n */\nexport const sync = (feed: Feed, options?: SyncOptions): Effect.Effect<void, never, FeedService> =>\n  Effect.gen(function* () {\n    const service = yield* FeedService;\n    yield* Effect.promise(() => service.sync(feed, options));\n  });\n\n/**\n * Returns queue replication backlog for the feed's namespace.\n *\n * @example\n * ```ts\n * const { blocksToPull, blocksToPush } = yield* Feed.getSyncState(feed);\n * ```\n */\nexport const getSyncState = (feed: Feed): Effect.Effect<SyncState, never, FeedService> =>\n  Effect.gen(function* () {\n    const service = yield* FeedService;\n    return yield* Effect.promise(() => service.getSyncState(feed));\n  });\n\n/**\n * Creates a cursor for iterating over feed items.\n * Currently stubbed — cursor operations are not yet implemented.\n *\n * @example\n * ```ts\n * const cursor = yield* Feed.cursor<Person>(feed);\n * const item = yield* Feed.next(cursor);\n * ```\n */\n// TODO(wittjosiah): Implement cursor operations. Use Effect streams?\nexport const cursor = <T = Obj.Snapshot>(_feed: Feed): Effect.Effect<Cursor<T>, never, FeedService> =>\n  Effect.succeed({ _tag: 'Cursor' } as Cursor<T>);\n\n/**\n * Returns the next item from a feed cursor.\n * Currently stubbed — cursor operations are not yet implemented.\n */\nexport const next = <T = Obj.Snapshot>(_cursor: Cursor<T>): Effect.Effect<T, never, FeedService> =>\n  Effect.die('Feed.next is not yet implemented');\n\n/**\n * Returns the next item from a feed cursor as an Option.\n * Currently stubbed — cursor operations are not yet implemented.\n */\nexport const nextOption = <T = Obj.Snapshot>(_cursor: Cursor<T>): Effect.Effect<Option.Option<T>, never, FeedService> =>\n  Effect.die('Feed.nextOption is not yet implemented');\n\n/**\n * Sets the local retention policy for a feed.\n * Currently stubbed — feeds do not yet support retention.\n *\n * @example\n * ```ts\n * yield* Feed.setRetention(feed, { count: 1000 });\n * ```\n */\n// TODO(dmaretskyi): Implement when feed retention is supported.\nexport const setRetention = (_feed: Feed, _options: RetentionOptions): Effect.Effect<void, never, FeedService> =>\n  Effect.void;\n"],
  "mappings": "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",
  "names": ["make", "Context", "Effect", "Layer", "Schema", "DXN", "EID", "Feed", "Struct", "name", "String", "pipe", "optional", "kind", "FormInputAnnotation", "set", "namespace", "Literal", "HiddenAnnotation", "IconAnnotation", "icon", "hue", "makeObject", "DXN", "make", "props", "getQueueUri", "feed", "EID", "tryParse", "getURI", "FeedService", "Tag", "Service", "ContextFeedService", "layer", "succeed", "notAvailable", "append", "Error", "remove", "query", "sync", "getSyncState", "items", "gen", "service", "promise", "ids", "map", "item", "id", "queryOrFilter", "runQuery", "flatMap", "queryResult", "run", "options", "cursor", "_feed", "_tag", "next", "_cursor", "die", "nextOption", "setRetention", "_options", "void"]
}
