2424use Illuminate \Database \Eloquent \Relations \Relation ;
2525use Illuminate \Database \Query \Builder as QueryBuilder ;
2626use InvalidArgumentException ;
27+ use LaravelJsonApi \Contracts \Schema \ID ;
28+ use LaravelJsonApi \Core \Schema \IdParser ;
2729use LogicException ;
2830use OutOfRangeException ;
31+ use RuntimeException ;
2932use function in_array ;
3033
3134class CursorBuilder
@@ -36,6 +39,11 @@ class CursorBuilder
3639 */
3740 private $ query ;
3841
42+ /**
43+ * @var ID|null
44+ */
45+ private ?ID $ id = null ;
46+
3947 /**
4048 * @var string
4149 */
@@ -81,6 +89,17 @@ public function __construct($query, string $column = null, string $key = null)
8189 $ this ->direction = 'desc ' ;
8290 }
8391
92+ /**
93+ * @param ID|null $id
94+ * @return $this
95+ */
96+ public function withIdField (?ID $ id ): self
97+ {
98+ $ this ->id = $ id ;
99+
100+ return $ this ;
101+ }
102+
84103 /**
85104 * Set the query direction.
86105 *
@@ -178,7 +197,8 @@ private function getQualifiedKeyName(): string
178197 private function next (Cursor $ cursor , $ columns ): CursorPaginator
179198 {
180199 if ($ cursor ->isAfter ()) {
181- $ this ->whereId ($ cursor ->getAfter (), $ this ->isDescending () ? '< ' : '> ' );
200+ $ afterId = $ this ->decodeId ($ cursor ->getAfter ());
201+ $ this ->whereId ($ afterId , $ this ->isDescending () ? '< ' : '> ' );
182202 }
183203
184204 $ items = $ this
@@ -187,12 +207,15 @@ private function next(Cursor $cursor, $columns): CursorPaginator
187207
188208 $ more = $ items ->count () > $ cursor ->getLimit ();
189209
190- return new CursorPaginator (
210+ $ paginator = new CursorPaginator (
191211 $ items ->slice (0 , $ cursor ->getLimit ()),
192212 $ more ,
193213 $ cursor ,
194214 $ this ->getUnqualifiedKey ()
195215 );
216+ $ paginator ->withIdField ($ this ->id );
217+
218+ return $ paginator ;
196219 }
197220
198221 /**
@@ -212,14 +235,36 @@ private function next(Cursor $cursor, $columns): CursorPaginator
212235 */
213236 private function previous (Cursor $ cursor , $ columns ): CursorPaginator
214237 {
238+ $ beforeId = $ this ->decodeId ($ cursor ->getBefore ());
239+
215240 $ items = $ this
216- ->whereId ($ cursor -> getBefore () , $ this ->isDescending () ? '> ' : '< ' )
241+ ->whereId ($ beforeId , $ this ->isDescending () ? '> ' : '< ' )
217242 ->orderForPrevious ()
218243 ->get ($ cursor ->getLimit (), $ columns )
219244 ->reverse ()
220245 ->values ();
221246
222- return new CursorPaginator ($ items , true , $ cursor , $ this ->getUnqualifiedKey ());
247+ $ paginator = new CursorPaginator ($ items , true , $ cursor , $ this ->getUnqualifiedKey ());
248+ $ paginator ->withIdField ($ this ->id );
249+
250+ return $ paginator ;
251+ }
252+
253+ /**
254+ * @param int|string $id
255+ * @return int|string
256+ */
257+ private function decodeId ($ id )
258+ {
259+ $ decoded = IdParser::make ($ this ->id )->decodeIfMatch ($ id );
260+
261+ if (null === $ decoded ) {
262+ throw new RuntimeException (
263+ "Cursor key {$ id } did not decode. Use validation to ensure the key matches the expected pattern. "
264+ );
265+ }
266+
267+ return $ decoded ;
223268 }
224269
225270 /**
0 commit comments