@@ -22,11 +22,22 @@ import { Code, FirestoreError } from '../util/error';
2222
2323export const DOCUMENT_KEY_NAME = '__name__' ;
2424
25+ /*!
26+ * A regular expression to verify an absolute Resource Path in Firestore. It
27+ * extracts the project ID, the database name and the relative resource path
28+ * if available.
29+ *
30+ * @type {RegExp }
31+ */
32+ const RESOURCE_PATH_RE =
33+ // Note: [\s\S] matches all characters including newlines.
34+ / ^ p r o j e c t s \/ ( [ ^ / ] * ) \/ d a t a b a s e s \/ ( [ ^ / ] * ) (?: \/ d o c u m e n t s \/ ) ? ( [ \s \S ] * ) $ / ;
35+
2536/**
2637 * Path represents an ordered sequence of string segments.
2738 */
2839abstract class BasePath < B extends BasePath < B > > {
29- private segments : string [ ] ;
40+ protected segments : string [ ] ;
3041 private offset : number ;
3142 private len : number ;
3243
@@ -257,6 +268,21 @@ export class ResourcePath extends BasePath<ResourcePath> {
257268 return this . toArray ( ) . map ( encodeURIComponent ) . join ( '/' ) ;
258269 }
259270
271+ /**
272+ * Converts this path to a fully qualified ResourcePath.
273+ *
274+ * @private
275+ * @internal
276+ * @param projectId The project ID of the current Firestore project.
277+ * @return A fully-qualified resource path pointing to the same element.
278+ */
279+ toQualifiedResourcePath (
280+ projectId : string ,
281+ databaseId : string
282+ ) : QualifiedResourcePath {
283+ return new QualifiedResourcePath ( projectId , databaseId , ...this . segments ) ;
284+ }
285+
260286 /**
261287 * Creates a resource path from the given slash-delimited string. If multiple
262288 * arguments are provided, all components are combined. Leading and trailing
@@ -287,6 +313,183 @@ export class ResourcePath extends BasePath<ResourcePath> {
287313 }
288314}
289315
316+ /**
317+ * A slash-separated path that includes a project and database ID for referring
318+ * to resources in any Firestore project.
319+ *
320+ * @private
321+ * @internal
322+ */
323+ export class QualifiedResourcePath extends ResourcePath {
324+ /**
325+ * The project ID of this path.
326+ */
327+ readonly projectId : string ;
328+
329+ /**
330+ * The database ID of this path.
331+ */
332+ readonly databaseId : string ;
333+
334+ /**
335+ * Constructs a Firestore Resource Path.
336+ *
337+ * @private
338+ * @internal
339+ * @param projectId The Firestore project id.
340+ * @param databaseId The Firestore database id.
341+ * @param segments Sequence of names of the parts of the path.
342+ */
343+ constructor ( projectId : string , databaseId : string , ...segments : string [ ] ) {
344+ super ( segments ) ;
345+
346+ this . projectId = projectId ;
347+ this . databaseId = databaseId ;
348+ }
349+
350+ /**
351+ * String representation of the path relative to the database root.
352+ * @private
353+ * @internal
354+ */
355+ get relativeName ( ) : string {
356+ return this . segments . join ( '/' ) ;
357+ }
358+
359+ /**
360+ * Creates a resource path from an absolute Firestore path.
361+ *
362+ * @private
363+ * @internal
364+ * @param absolutePath A string representation of a Resource Path.
365+ * @returns The new ResourcePath.
366+ */
367+ static fromSlashSeparatedString ( absolutePath : string ) : QualifiedResourcePath {
368+ const elements = RESOURCE_PATH_RE . exec ( absolutePath ) ;
369+
370+ if ( elements ) {
371+ const project = elements [ 1 ] ;
372+ const database = elements [ 2 ] ;
373+ const path = elements [ 3 ] ;
374+ return new QualifiedResourcePath ( project , database ) . append ( path ) ;
375+ }
376+
377+ throw new Error ( `Resource name '${ absolutePath } ' is not valid.` ) ;
378+ }
379+
380+ /**
381+ * Create a child path beneath the current level.
382+ *
383+ * @private
384+ * @internal
385+ * @param relativePath Relative path to append to the current path.
386+ * @returns The new path.
387+ */
388+ append ( relativePath : ResourcePath | string ) : QualifiedResourcePath {
389+ // `super.append()` calls `QualifiedResourcePath.construct()` when invoked
390+ // from here and returns a QualifiedResourcePath.
391+ return super . append ( relativePath ) as QualifiedResourcePath ;
392+ }
393+
394+ /**
395+ * Create a child path beneath the current level.
396+ *
397+ * @private
398+ * @internal
399+ * @returns The new path.
400+ */
401+ parent ( ) : QualifiedResourcePath | null {
402+ return super . parent ( ) as QualifiedResourcePath | null ;
403+ }
404+
405+ /**
406+ * String representation of a ResourcePath as expected by the API.
407+ *
408+ * @private
409+ * @internal
410+ * @returns The representation as expected by the API.
411+ */
412+ get formattedName ( ) : string {
413+ const components = [
414+ 'projects' ,
415+ this . projectId ,
416+ 'databases' ,
417+ this . databaseId ,
418+ 'documents' ,
419+ ...this . segments
420+ ] ;
421+ return components . join ( '/' ) ;
422+ }
423+
424+ /**
425+ * Constructs a new instance of ResourcePath. We need this instead of using
426+ * the normal constructor because polymorphic 'this' doesn't work on static
427+ * methods.
428+ *
429+ * @private
430+ * @internal
431+ * @param segments Sequence of names of the parts of the path.
432+ * @returns The newly created QualifiedResourcePath.
433+ */
434+ construct ( segments : string [ ] ) : QualifiedResourcePath {
435+ return new QualifiedResourcePath (
436+ this . projectId ,
437+ this . databaseId ,
438+ ...segments
439+ ) ;
440+ }
441+
442+ /**
443+ * Convenience method to match the ResourcePath API. This method always
444+ * returns the current instance.
445+ *
446+ * @private
447+ * @internal
448+ */
449+ toQualifiedResourcePath ( ) : QualifiedResourcePath {
450+ return this ;
451+ }
452+
453+ /**
454+ * Compare the current path against another ResourcePath object.
455+ *
456+ * @private
457+ * @internal
458+ * @param other The path to compare to.
459+ * @returns -1 if current < other, 1 if current > other, 0 if equal
460+ */
461+ compareTo ( other : ResourcePath ) : number {
462+ if ( other instanceof QualifiedResourcePath ) {
463+ if ( this . projectId < other . projectId ) {
464+ return - 1 ;
465+ }
466+ if ( this . projectId > other . projectId ) {
467+ return 1 ;
468+ }
469+
470+ if ( this . databaseId < other . databaseId ) {
471+ return - 1 ;
472+ }
473+ if ( this . databaseId > other . databaseId ) {
474+ return 1 ;
475+ }
476+ }
477+
478+ return super . compareTo ( other ) ;
479+ }
480+
481+ /**
482+ * Converts this ResourcePath to the Firestore Proto representation.
483+ * @private
484+ * @internal
485+ */
486+ toProto ( ) : api . IValue {
487+ return {
488+ referenceValue : this . formattedName
489+ } ;
490+ }
491+ }
492+
290493const identifierRegExp = / ^ [ _ a - z A - Z ] [ _ a - z A - Z 0 - 9 ] * $ / ;
291494
292495/**
0 commit comments