diff --git a/core/lib/Http/Response/StreamedNdJsonResponse.php b/core/lib/Http/Response/StreamedNdJsonResponse.php new file mode 100644 index 0000000..dd0429a --- /dev/null +++ b/core/lib/Http/Response/StreamedNdJsonResponse.php @@ -0,0 +1,76 @@ + 1, 'name' => 'Alice']; + * yield ['id' => 2, 'name' => 'Bob']; + * } + * + * return new StreamedNdJsonResponse(records()); + */ +class StreamedNdJsonResponse extends StreamedResponse +{ + /** + * @param iterable $items Items to serialize; each becomes one JSON line + * @param int $flushInterval Flush to client after this many items (default 10) + * @param int $status HTTP status code (default 200) + * @param array $headers Additional HTTP headers + * @param int $encodingOptions Flags passed to json_encode() + */ + public function __construct( + iterable $items, + int $flushInterval = 10, + int $status = 200, + array $headers = [], + private readonly int $encodingOptions = JsonResponse::DEFAULT_ENCODING_OPTIONS, + ) { + parent::__construct(null, $status, $headers); + + if (!$this->headers->get('Content-Type')) { + $this->headers->set('Content-Type', 'application/x-ndjson'); + } + + if (!$this->headers->has('X-Accel-Buffering')) { + $this->headers->set('X-Accel-Buffering', 'no'); + } + + $encodingOptions = $this->encodingOptions; + + $this->setCallback(static function () use ($items, $flushInterval, $encodingOptions): void { + $count = 0; + + foreach ($items as $item) { + echo json_encode($item, \JSON_THROW_ON_ERROR | $encodingOptions) . "\n"; + $count++; + + if ($count >= $flushInterval) { + @ob_flush(); + flush(); + $count = 0; + } + } + + // final flush for any remaining buffered items + if ($count > 0) { + @ob_flush(); + flush(); + } + }); + } +} diff --git a/shared/lib/Mail/Service/ServiceBaseInterface.php b/shared/lib/Mail/Service/ServiceBaseInterface.php index 4de6e5c..9745c4b 100644 --- a/shared/lib/Mail/Service/ServiceBaseInterface.php +++ b/shared/lib/Mail/Service/ServiceBaseInterface.php @@ -9,6 +9,7 @@ declare(strict_types=1); namespace KTXF\Mail\Service; +use Generator; use KTXF\Mail\Collection\CollectionBaseInterface; use KTXF\Mail\Object\AddressInterface; use KTXF\Resource\Delta\Delta; @@ -168,6 +169,21 @@ interface ServiceBaseInterface extends ResourceServiceBaseInterface { */ public function entityList(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): array; + /** + * Lists messages in a collection + * + * @since 2025.05.01 + * + * @param string|int $collection Collection ID + * @param IFilter|null $filter Optional filter criteria + * @param ISort|null $sort Optional sort order + * @param IRange|null $range Optional pagination + * @param array|null $properties Optional message properties to fetch + * + * @return Generator Yields messages one by one as EntityBaseInterface + */ + public function entityListStream(string|int $collection, ?IFilter $filter = null, ?ISort $sort = null, ?IRange $range = null, ?array $properties = null): Generator; + /** * Creates a filter builder for messages *