1- import { Effect } from 'effect'
1+ import { Chunk as EffectChunk , Effect , Stream } from 'effect'
22import {
33 chunk as chunkInternal ,
44 streamChunks as streamChunksInternal ,
@@ -7,7 +7,14 @@ import { extractEntities } from './extract'
77import { parseCode } from './parser'
88import { detectLanguage } from './parser/languages'
99import { buildScopeTree } from './scope'
10- import type { Chunk , ChunkOptions , Language } from './types'
10+ import type {
11+ Chunk ,
12+ ChunkOptions ,
13+ ExtractedEntity ,
14+ Language ,
15+ ParseResult ,
16+ ScopeTree ,
17+ } from './types'
1118
1219/**
1320 * Error thrown when chunking fails
@@ -138,6 +145,114 @@ export async function chunk(
138145 return Effect . runPromise ( chunkEffect ( filepath , code , options ) )
139146}
140147
148+ /**
149+ * Prepare the chunking pipeline (parse, extract, build scope tree)
150+ * Returns the parsed result and scope tree needed for chunking
151+ */
152+ const prepareChunking = (
153+ filepath : string ,
154+ code : string ,
155+ options ?: ChunkOptions ,
156+ ) : Effect . Effect <
157+ { parseResult : ParseResult ; scopeTree : ScopeTree ; language : Language } ,
158+ ChunkingError | UnsupportedLanguageError
159+ > => {
160+ return Effect . gen ( function * ( ) {
161+ // Step 1: Detect language (or use override)
162+ const language : Language | null =
163+ options ?. language ?? detectLanguage ( filepath )
164+
165+ if ( ! language ) {
166+ return yield * Effect . fail ( new UnsupportedLanguageError ( filepath ) )
167+ }
168+
169+ // Step 2: Parse the code
170+ const parseResult = yield * Effect . tryPromise ( {
171+ try : ( ) => parseCode ( code , language ) ,
172+ catch : ( error : unknown ) =>
173+ new ChunkingError ( 'Failed to parse code' , error ) ,
174+ } )
175+
176+ // Step 3: Extract entities from AST
177+ const entities = yield * Effect . mapError (
178+ extractEntities ( parseResult . tree . rootNode , language , code ) ,
179+ ( error : unknown ) =>
180+ new ChunkingError ( 'Failed to extract entities' , error ) ,
181+ )
182+
183+ // Step 4: Build scope tree
184+ const scopeTree = yield * Effect . mapError (
185+ buildScopeTree ( entities ) ,
186+ ( error : unknown ) =>
187+ new ChunkingError ( 'Failed to build scope tree' , error ) ,
188+ )
189+
190+ return { parseResult, scopeTree, language }
191+ } )
192+ }
193+
194+ /**
195+ * Create an Effect Stream that yields chunks
196+ *
197+ * This is the Effect-native streaming API. Use this if you're working
198+ * within the Effect ecosystem and want full composability.
199+ *
200+ * @param filepath - The file path (used for language detection)
201+ * @param code - The source code to chunk
202+ * @param options - Optional chunking configuration
203+ * @returns Effect Stream of chunks with context
204+ *
205+ * @example
206+ * ```ts
207+ * import { chunkStreamEffect } from 'astchunk'
208+ * import { Effect, Stream } from 'effect'
209+ *
210+ * const program = Stream.runForEach(
211+ * chunkStreamEffect('src/utils.ts', sourceCode),
212+ * (chunk) => Effect.log(chunk.text)
213+ * )
214+ *
215+ * Effect.runPromise(program)
216+ * ```
217+ */
218+ export const chunkStreamEffect = (
219+ filepath : string ,
220+ code : string ,
221+ options ?: ChunkOptions ,
222+ ) : Stream . Stream < Chunk , ChunkingError | UnsupportedLanguageError > => {
223+ return Stream . unwrap (
224+ Effect . map ( prepareChunking ( filepath , code , options ) , ( prepared ) => {
225+ const { parseResult, scopeTree, language } = prepared
226+
227+ // Create stream from the internal generator
228+ return Stream . fromAsyncIterable (
229+ streamChunksInternal (
230+ parseResult . tree . rootNode ,
231+ code ,
232+ scopeTree ,
233+ language ,
234+ options ,
235+ filepath ,
236+ ) ,
237+ ( error ) => new ChunkingError ( 'Stream iteration failed' , error ) ,
238+ ) . pipe (
239+ // Attach parse error to chunks if present
240+ Stream . map ( ( chunk ) =>
241+ parseResult . error
242+ ? {
243+ ...chunk ,
244+ context : {
245+ ...chunk . context ,
246+ parseError : parseResult . error ,
247+ } ,
248+ }
249+ : chunk ,
250+ ) ,
251+ )
252+ } ) ,
253+ )
254+ }
255+
141256/**
142257 * Stream source code chunks as they are generated
143258 *
@@ -154,9 +269,9 @@ export async function chunk(
154269 *
155270 * @example
156271 * ```ts
157- * import { stream } from 'astchunk'
272+ * import { chunkStream } from 'astchunk'
158273 *
159- * for await (const chunk of stream ('src/utils.ts', sourceCode)) {
274+ * for await (const chunk of chunkStream ('src/utils.ts', sourceCode)) {
160275 * console.log(chunk.text, chunk.context)
161276 * }
162277 * ```
@@ -166,49 +281,14 @@ export async function* chunkStream(
166281 code : string ,
167282 options ?: ChunkOptions ,
168283) : AsyncGenerator < Chunk > {
169- // Detect language (or use override)
170- const language : Language | null =
171- options ?. language ?? detectLanguage ( filepath )
172-
173- if ( ! language ) {
174- throw new UnsupportedLanguageError ( filepath )
175- }
176-
177- // Parse the code
178- let parseResult : Awaited < ReturnType < typeof parseCode > >
179- try {
180- parseResult = await parseCode ( code , language )
181- } catch ( error ) {
182- throw new ChunkingError ( 'Failed to parse code' , error )
183- }
184-
185- // Extract entities from AST
186- let entities : Awaited <
187- ReturnType < typeof extractEntities > extends Effect . Effect < infer A , unknown >
188- ? A
189- : never
190- >
191- try {
192- entities = await Effect . runPromise (
193- extractEntities ( parseResult . tree . rootNode , language , code ) ,
194- )
195- } catch ( error ) {
196- throw new ChunkingError ( 'Failed to extract entities' , error )
197- }
284+ // Prepare the chunking pipeline
285+ const prepared = await Effect . runPromise (
286+ prepareChunking ( filepath , code , options ) ,
287+ )
198288
199- // Build scope tree
200- let scopeTree : Awaited <
201- ReturnType < typeof buildScopeTree > extends Effect . Effect < infer A , unknown >
202- ? A
203- : never
204- >
205- try {
206- scopeTree = await Effect . runPromise ( buildScopeTree ( entities ) )
207- } catch ( error ) {
208- throw new ChunkingError ( 'Failed to build scope tree' , error )
209- }
289+ const { parseResult, scopeTree, language } = prepared
210290
211- // Stream chunks from the internal generator, passing filepath for context
291+ // Stream chunks from the internal generator
212292 const chunkGenerator = streamChunksInternal (
213293 parseResult . tree . rootNode ,
214294 code ,
0 commit comments