1616 */
1717
1818import { assert } from '@firebase/util' ;
19+ import {
20+ tryParseInt ,
21+ MAX_NAME ,
22+ MIN_NAME ,
23+ INTEGER_32_MIN ,
24+ INTEGER_32_MAX
25+ } from '../util/util' ;
26+
27+ // Modeled after base64 web-safe chars, but ordered by ASCII.
28+ const PUSH_CHARS =
29+ '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' ;
30+
31+ const MIN_PUSH_CHAR = '-' ;
32+
33+ const MAX_PUSH_CHAR = 'z' ;
34+
35+ const MAX_KEY_LEN = 786 ;
1936
2037/**
2138 * Fancy ID generator that creates 20-character string identifiers with the
@@ -32,10 +49,6 @@ import { assert } from '@firebase/util';
3249 * in the case of a timestamp collision).
3350 */
3451export const nextPushId = ( function ( ) {
35- // Modeled after base64 web-safe chars, but ordered by ASCII.
36- const PUSH_CHARS =
37- '-0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz' ;
38-
3952 // Timestamp of last push, used to prevent local collisions if you push twice
4053 // in one ms.
4154 let lastPushTime = 0 ;
@@ -82,3 +95,83 @@ export const nextPushId = (function () {
8295 return id ;
8396 } ;
8497} ) ( ) ;
98+
99+ export const successor = function ( key : string ) {
100+ if ( key === '' + INTEGER_32_MAX ) {
101+ // See https://firebase.google.com/docs/database/web/lists-of-data#data-order
102+ return MIN_PUSH_CHAR ;
103+ }
104+ const keyAsInt : number = tryParseInt ( key ) ;
105+ if ( keyAsInt != null ) {
106+ return '' + ( keyAsInt + 1 ) ;
107+ }
108+ const next = new Array ( key . length ) ;
109+
110+ for ( let i = 0 ; i < next . length ; i ++ ) {
111+ next [ i ] = key . charAt ( i ) ;
112+ }
113+
114+ if ( next . length < MAX_KEY_LEN ) {
115+ next . push ( MIN_PUSH_CHAR ) ;
116+ return next . join ( '' ) ;
117+ }
118+
119+ let i = next . length - 1 ;
120+
121+ while ( i >= 0 && next [ i ] === MAX_PUSH_CHAR ) {
122+ i -- ;
123+ }
124+
125+ // `successor` was called on the largest possible key, so return the
126+ // MAX_NAME, which sorts larger than all keys.
127+ if ( i === - 1 ) {
128+ return MAX_NAME ;
129+ }
130+
131+ const source = next [ i ] ;
132+ const sourcePlusOne = PUSH_CHARS . charAt ( PUSH_CHARS . indexOf ( source ) + 1 ) ;
133+ next [ i ] = sourcePlusOne ;
134+
135+ return next . slice ( 0 , i + 1 ) . join ( '' ) ;
136+ } ;
137+
138+ // `key` is assumed to be non-empty.
139+ export const predecessor = function ( key : string ) {
140+ if ( key === '' + INTEGER_32_MIN ) {
141+ return MIN_NAME ;
142+ }
143+ const keyAsInt : number = tryParseInt ( key ) ;
144+ if ( keyAsInt != null ) {
145+ return '' + ( keyAsInt - 1 ) ;
146+ }
147+ const next = new Array ( key . length ) ;
148+ for ( let i = 0 ; i < next . length ; i ++ ) {
149+ next [ i ] = key . charAt ( i ) ;
150+ }
151+ // If `key` ends in `MIN_PUSH_CHAR`, the largest key lexicographically
152+ // smaller than `key`, is `key[0:key.length - 1]`. The next key smaller
153+ // than that, `predecessor(predecessor(key))`, is
154+ //
155+ // `key[0:key.length - 2] + (key[key.length - 1] - 1) + \
156+ // { MAX_PUSH_CHAR repeated MAX_KEY_LEN - (key.length - 1) times }
157+ //
158+ // analogous to increment/decrement for base-10 integers.
159+ //
160+ // This works because lexigographic comparison works character-by-character,
161+ // using length as a tie-breaker if one key is a prefix of the other.
162+ if ( next [ next . length - 1 ] === MIN_PUSH_CHAR ) {
163+ if ( next . length === 1 ) {
164+ // See https://firebase.google.com/docs/database/web/lists-of-data#orderbykey
165+ return '' + INTEGER_32_MAX ;
166+ }
167+ delete next [ next . length - 1 ] ;
168+ return next . join ( '' ) ;
169+ }
170+ // Replace the last character with it's immediate predecessor, and
171+ // fill the suffix of the key with MAX_PUSH_CHAR. This is the
172+ // lexicographically largest possible key smaller than `key`.
173+ next [ next . length - 1 ] = PUSH_CHARS . charAt (
174+ PUSH_CHARS . indexOf ( next [ next . length - 1 ] ) - 1
175+ ) ;
176+ return next . join ( '' ) + MAX_PUSH_CHAR . repeat ( MAX_KEY_LEN - next . length ) ;
177+ } ;
0 commit comments