@@ -17,13 +17,24 @@ function unescapeSegment(segment) {
1717 * @param {string } pointer - RFC 6901 pointer (e.g. "/data/items/0")
1818 * @returns {string[] } Unescaped segments (empty for root)
1919 */
20- function parsePointer ( pointer ) {
20+ export function parsePointer ( pointer ) {
2121 if ( ! pointer || typeof pointer !== 'string' ) return [ ] ;
2222 const trimmed = pointer . startsWith ( '/' ) ? pointer . slice ( 1 ) : pointer ;
2323 if ( ! trimmed ) return [ ] ;
2424 return trimmed . split ( '/' ) . map ( unescapeSegment ) ;
2525}
2626
27+ /**
28+ * Get parent pointer (pointer without last segment).
29+ * @param {string } pointer - RFC 6901 pointer (e.g. "/data/items/0")
30+ * @returns {string } Parent pointer (e.g. "/data/items") or empty string for root
31+ */
32+ export function getParentPointer ( pointer ) {
33+ const segments = parsePointer ( pointer ) ;
34+ if ( segments . length <= 1 ) return '' ;
35+ return `/${ segments . slice ( 0 , - 1 ) . map ( ( s ) => escapeSegment ( String ( s ) ) ) . join ( '/' ) } ` ;
36+ }
37+
2738/**
2839 * Append segment to pointer.
2940 * @param {string } pointer - Base pointer
@@ -127,3 +138,63 @@ export function removeValue(data, pointer) {
127138 }
128139 return false ;
129140}
141+
142+ /**
143+ * Move array item before another item, or to end if beforePointer is empty.
144+ * @param {Object } data - Root object
145+ * @param {string } pointer - RFC 6901 pointer to the item to move
146+ * @param {string } [beforePointer] - Pointer to the item before which to insert, or empty for append
147+ * @returns {boolean } True if moved
148+ */
149+ export function moveBefore ( data , pointer , beforePointer ) {
150+ const parentPointer = getParentPointer ( pointer ) ;
151+ const array = getValue ( data , parentPointer ) ;
152+ if ( ! parentPointer || ! Array . isArray ( array ) ) return false ;
153+
154+ const currentIndex = parseInt ( parsePointer ( pointer ) . pop ( ) , 10 ) ;
155+ if ( currentIndex < 0 || currentIndex >= array . length ) return false ;
156+
157+ const isEmpty = ! beforePointer || ! String ( beforePointer ) . trim ( ) ;
158+ let targetIndex ;
159+ if ( isEmpty ) {
160+ targetIndex = array . length ;
161+ } else {
162+ if ( getParentPointer ( beforePointer ) !== parentPointer ) return false ;
163+ targetIndex = Math . max ( 0 , Math . min (
164+ parseInt ( parsePointer ( beforePointer ) . pop ( ) , 10 ) ,
165+ array . length ,
166+ ) ) ;
167+ }
168+
169+ if ( currentIndex === targetIndex ) return false ;
170+
171+ const [ item ] = array . splice ( currentIndex , 1 ) ;
172+ array . splice ( targetIndex , 0 , item ) ;
173+ return true ;
174+ }
175+
176+ /**
177+ * Insert value before the item at pointer.
178+ * @param {Object } data - Root object
179+ * @param {string } pointer - RFC 6901 pointer to the item or append position
180+ * @param {* } value - Value to insert
181+ * @returns {boolean } True if inserted
182+ */
183+ export function insertBefore ( data , pointer , value ) {
184+ const parentPointer = getParentPointer ( pointer ) ;
185+ if ( ! parentPointer ) return false ;
186+
187+ const segments = parsePointer ( pointer ) ;
188+ const index = parseInt ( segments [ segments . length - 1 ] , 10 ) ;
189+ if ( ! Number . isInteger ( index ) || index < 0 ) return false ;
190+
191+ let array = getValue ( data , parentPointer ) ;
192+ if ( ! Array . isArray ( array ) ) {
193+ array = [ ] ;
194+ setValue ( data , parentPointer , array ) ;
195+ }
196+
197+ const clampedIndex = Math . max ( 0 , Math . min ( index , array . length ) ) ;
198+ array . splice ( clampedIndex , 0 , value ) ;
199+ return true ;
200+ }
0 commit comments