firebase-rest-firestore 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,7 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.convertToFirestoreValue = convertToFirestoreValue;
4
4
  exports.convertFromFirestoreValue = convertFromFirestoreValue;
5
5
  exports.convertToFirestoreDocument = convertToFirestoreDocument;
6
+ exports.extractFieldTransforms = extractFieldTransforms;
7
+ exports.buildCommitWrite = buildCommitWrite;
6
8
  exports.convertFromFirestoreDocument = convertFromFirestoreDocument;
9
+ const client_1 = require("../client");
10
+ const field_value_1 = require("../field-value");
11
+ const types_1 = require("../types");
7
12
  const path_1 = require("./path");
8
13
  /**
9
14
  * JSの値をFirestore形式に変換する
@@ -11,9 +16,24 @@ const path_1 = require("./path");
11
16
  * @returns Firestore形式の値
12
17
  */
13
18
  function convertToFirestoreValue(value) {
19
+ if (value instanceof field_value_1.FieldValue) {
20
+ // Sentinels (e.g. serverTimestamp) must be extracted into field transforms
21
+ // before conversion. Reaching here means one was used where Firestore
22
+ // cannot express a transform (such as inside an array).
23
+ throw new Error("FieldValue (e.g. serverTimestamp()) can only be used as a top-level or nested document field value, not inside an array.");
24
+ }
14
25
  if (value instanceof Date) {
15
26
  return { timestampValue: value.toISOString() };
16
27
  }
28
+ else if (value instanceof client_1.DocumentReference) {
29
+ return { referenceValue: value.referenceValue };
30
+ }
31
+ else if (value instanceof types_1.LiteralDocumentReference) {
32
+ return { referenceValue: value.referenceValue };
33
+ }
34
+ else if (value instanceof types_1.LiteralGeoPointValue) {
35
+ return { geoPointValue: value.geoPointValue };
36
+ }
17
37
  else if (typeof value === "string") {
18
38
  return { stringValue: value };
19
39
  }
@@ -69,15 +89,21 @@ function convertFromFirestoreValue(firestoreValue) {
69
89
  else if ("timestampValue" in firestoreValue) {
70
90
  return new Date(firestoreValue.timestampValue);
71
91
  }
92
+ else if ("geoPointValue" in firestoreValue) {
93
+ return new types_1.LiteralGeoPointValue(firestoreValue);
94
+ }
95
+ else if ("referenceValue" in firestoreValue) {
96
+ return new types_1.LiteralDocumentReference(firestoreValue);
97
+ }
72
98
  else if ("mapValue" in firestoreValue && firestoreValue.mapValue.fields) {
73
99
  return Object.entries(firestoreValue.mapValue.fields).reduce((acc, [key, val]) => ({
74
100
  ...acc,
75
101
  [key]: convertFromFirestoreValue(val),
76
102
  }), {});
77
103
  }
78
- else if ("arrayValue" in firestoreValue &&
79
- firestoreValue.arrayValue.values) {
80
- return firestoreValue.arrayValue.values.map(convertFromFirestoreValue);
104
+ else if ("arrayValue" in firestoreValue) {
105
+ // The `values` field can be undefined, meaning that this is an empty array
106
+ return (firestoreValue.arrayValue.values ?? []).map(convertFromFirestoreValue);
81
107
  }
82
108
  return null;
83
109
  }
@@ -94,6 +120,91 @@ function convertToFirestoreDocument(data) {
94
120
  }), {}),
95
121
  };
96
122
  }
123
+ /**
124
+ * Whether a value is a plain JS object (`{}` / `Object.create(null)`), as
125
+ * opposed to a class instance such as `Date`, `DocumentReference`,
126
+ * `LiteralGeoPointValue`, `LiteralDocumentReference`, `FieldValue`, or an array.
127
+ * Only plain objects are recursed into when extracting field transforms.
128
+ */
129
+ function isPlainObject(value) {
130
+ if (value === null || typeof value !== "object")
131
+ return false;
132
+ const proto = Object.getPrototypeOf(value);
133
+ return proto === Object.prototype || proto === null;
134
+ }
135
+ /**
136
+ * Escape a single field name for use in a Firestore field path. Simple names
137
+ * (`[A-Za-z_][A-Za-z0-9_]*`) are used as-is; anything else (dashes, dots,
138
+ * leading digits, spaces, ...) is wrapped in backticks with `\` and `` ` ``
139
+ * escaped, so a literal dot in a key is treated as part of the name rather than
140
+ * a path separator.
141
+ * See: https://firebase.google.com/docs/firestore/reference/rest/v1/projects.databases.documents#Document
142
+ */
143
+ function escapeFieldPathSegment(segment) {
144
+ if (/^[A-Za-z_][A-Za-z0-9_]*$/.test(segment)) {
145
+ return segment;
146
+ }
147
+ return "`" + segment.replace(/\\/g, "\\\\").replace(/`/g, "\\`") + "`";
148
+ }
149
+ /**
150
+ * Split write data into plain field values and Firestore field transforms.
151
+ *
152
+ * `FieldValue` sentinels (e.g. `serverTimestamp()`) are pulled out into
153
+ * transforms keyed by their (escaped, dot-separated) field path; everything
154
+ * else is left untouched in `fields`. Recursion only descends into plain
155
+ * objects, so class instances (Date / references / geo points) are treated as
156
+ * leaves.
157
+ *
158
+ * @param data Write data (JS values, may contain FieldValue sentinels)
159
+ * @param prefix Field-path prefix used while recursing (internal, pre-escaped)
160
+ */
161
+ function extractFieldTransforms(data, prefix = "") {
162
+ const fields = {};
163
+ const transforms = [];
164
+ for (const [key, value] of Object.entries(data)) {
165
+ const escapedKey = escapeFieldPathSegment(key);
166
+ const fieldPath = prefix ? `${prefix}.${escapedKey}` : escapedKey;
167
+ if (value instanceof field_value_1.FieldValue) {
168
+ if (value.methodName === "serverTimestamp") {
169
+ transforms.push({ fieldPath, setToServerValue: "REQUEST_TIME" });
170
+ }
171
+ else {
172
+ throw new Error(`Unsupported FieldValue: ${value.methodName}`);
173
+ }
174
+ }
175
+ else if (isPlainObject(value)) {
176
+ const nested = extractFieldTransforms(value, fieldPath);
177
+ fields[key] = nested.fields;
178
+ transforms.push(...nested.transforms);
179
+ }
180
+ else {
181
+ fields[key] = value;
182
+ }
183
+ }
184
+ return { fields, transforms };
185
+ }
186
+ /**
187
+ * Build a single `documents:commit` write that updates a document and applies
188
+ * field transforms. `updateTransforms` / `currentDocument` are only included
189
+ * when relevant.
190
+ *
191
+ * @param documentName Full resource name (projects/.../documents/<path>)
192
+ * @param fields Already-converted Firestore field values
193
+ * @param transforms Field transforms to apply after the update
194
+ * @param currentDocument Optional precondition (e.g. `{ exists: false }`)
195
+ */
196
+ function buildCommitWrite(documentName, fields, transforms, currentDocument) {
197
+ const write = {
198
+ update: { name: documentName, fields },
199
+ };
200
+ if (transforms.length > 0) {
201
+ write.updateTransforms = transforms;
202
+ }
203
+ if (currentDocument) {
204
+ write.currentDocument = currentDocument;
205
+ }
206
+ return write;
207
+ }
97
208
  /**
98
209
  * Firestoreドキュメントをオブジェクトに変換
99
210
  * @param doc Firestoreレスポンス
@@ -30,6 +30,30 @@ export declare class FirestoreClient {
30
30
  * @private
31
31
  */
32
32
  private prepareHeaders;
33
+ /**
34
+ * Build the fully-qualified Firestore reference value for a document path,
35
+ * e.g. `projects/{projectId}/databases/{databaseId}/documents/{path}`.
36
+ * Used to encode a DocumentReference without exposing internal path helpers.
37
+ * @param documentPath Document path (ex: "users/uid/posts/postId")
38
+ */
39
+ getReferenceValue(documentPath: string): string;
40
+ /**
41
+ * Apply a single write through the `documents:commit` endpoint. This is the
42
+ * only REST path that supports field transforms (e.g. server timestamps).
43
+ * @param collectionName Collection path
44
+ * @param documentId Document ID
45
+ * @param fields Already-converted Firestore field values
46
+ * @param transforms Field transforms to apply after the update
47
+ * @param currentDocument Optional precondition (e.g. `{ exists: false }`)
48
+ * @private
49
+ */
50
+ private commit;
51
+ /**
52
+ * Commit a write and read the document back, so the resolved transform
53
+ * values (e.g. the server timestamp) are returned to the caller.
54
+ * @private
55
+ */
56
+ private commitAndRead;
33
57
  /**
34
58
  * Get collection reference
35
59
  * @param path Collection path
@@ -57,6 +81,14 @@ export declare class FirestoreClient {
57
81
  add(collectionName: string, data: Record<string, any>): Promise<Record<string, any> & {
58
82
  id: string;
59
83
  }>;
84
+ /**
85
+ * Create a document that contains field transforms (e.g. serverTimestamp)
86
+ * with an auto-generated ID. Uses the commit endpoint with an
87
+ * `exists: false` precondition and retries on the (astronomically unlikely)
88
+ * ID collision, mirroring the uniqueness of server-generated IDs.
89
+ * @private
90
+ */
91
+ private addWithTransforms;
60
92
  /**
61
93
  * Get document
62
94
  * @param collectionName Collection name
@@ -186,6 +218,11 @@ export declare class DocumentReference {
186
218
  * Get document path
187
219
  */
188
220
  get path(): string;
221
+ /**
222
+ * Fully-qualified Firestore reference value for this document, e.g.
223
+ * `projects/{projectId}/databases/{databaseId}/documents/{path}`.
224
+ */
225
+ get referenceValue(): string;
189
226
  /**
190
227
  * Get parent collection reference
191
228
  */
@@ -1,8 +1,19 @@
1
1
  import { getFirestoreToken } from "./utils/auth";
2
- import { convertFromFirestoreDocument, convertToFirestoreDocument, convertToFirestoreValue, } from "./utils/converter";
2
+ import { buildCommitWrite, convertFromFirestoreDocument, convertToFirestoreDocument, convertToFirestoreValue, extractFieldTransforms, } from "./utils/converter";
3
3
  import { getFirestoreBasePath } from "./utils/path";
4
4
  import { formatPrivateKey } from "./utils/config";
5
5
  import { createFirestorePath } from "./utils/path";
6
+ /**
7
+ * Generate a random 20-character document ID (same alphabet as the Firebase SDKs).
8
+ */
9
+ function generateAutoId() {
10
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
11
+ let id = "";
12
+ for (let i = 0; i < 20; i++) {
13
+ id += chars.charAt(Math.floor(Math.random() * chars.length));
14
+ }
15
+ return id;
16
+ }
6
17
  /**
7
18
  * Firestore client class
8
19
  */
@@ -90,6 +101,63 @@ export class FirestoreClient {
90
101
  }
91
102
  return headers;
92
103
  }
104
+ /**
105
+ * Build the fully-qualified Firestore reference value for a document path,
106
+ * e.g. `projects/{projectId}/databases/{databaseId}/documents/{path}`.
107
+ * Used to encode a DocumentReference without exposing internal path helpers.
108
+ * @param documentPath Document path (ex: "users/uid/posts/postId")
109
+ */
110
+ getReferenceValue(documentPath) {
111
+ return this.pathUtil.getParentReference(documentPath);
112
+ }
113
+ /**
114
+ * Apply a single write through the `documents:commit` endpoint. This is the
115
+ * only REST path that supports field transforms (e.g. server timestamps).
116
+ * @param collectionName Collection path
117
+ * @param documentId Document ID
118
+ * @param fields Already-converted Firestore field values
119
+ * @param transforms Field transforms to apply after the update
120
+ * @param currentDocument Optional precondition (e.g. `{ exists: false }`)
121
+ * @private
122
+ */
123
+ async commit(collectionName, documentId, fields, transforms, currentDocument) {
124
+ const documentName = this.pathUtil.getParentReference(`${collectionName}/${documentId}`);
125
+ const write = buildCommitWrite(documentName, fields, transforms, currentDocument);
126
+ const url = `${this.pathUtil.getBasePath()}:commit`;
127
+ if (this.debug) {
128
+ console.log(`Committing write to: ${url}`, JSON.stringify(write));
129
+ }
130
+ const headers = await this.prepareHeaders();
131
+ const response = await fetch(url, {
132
+ method: "POST",
133
+ headers,
134
+ body: JSON.stringify({ writes: [write] }),
135
+ });
136
+ if (!response.ok) {
137
+ const errorText = await response.text();
138
+ if (this.debug) {
139
+ console.error(`Error response: ${errorText}`);
140
+ }
141
+ const error = new Error(`Firestore API error: ${response.statusText || response.status} - ${errorText}`);
142
+ error.status = response.status;
143
+ error.alreadyExists =
144
+ response.status === 409 || /ALREADY_EXISTS/.test(errorText);
145
+ throw error;
146
+ }
147
+ }
148
+ /**
149
+ * Commit a write and read the document back, so the resolved transform
150
+ * values (e.g. the server timestamp) are returned to the caller.
151
+ * @private
152
+ */
153
+ async commitAndRead(collectionName, documentId, fields, transforms, currentDocument) {
154
+ await this.commit(collectionName, documentId, fields, transforms, currentDocument);
155
+ const saved = await this.get(collectionName, documentId);
156
+ if (!saved) {
157
+ throw new Error(`Document ${collectionName}/${documentId} could not be read back after commit`);
158
+ }
159
+ return saved;
160
+ }
93
161
  /**
94
162
  * Get collection reference
95
163
  * @param path Collection path
@@ -134,6 +202,12 @@ export class FirestoreClient {
134
202
  if (this.debug) {
135
203
  console.log(`Adding document to collection: ${collectionName}`, data);
136
204
  }
205
+ // When the data contains field transforms (e.g. serverTimestamp), the
206
+ // create must go through the commit endpoint with a client-generated ID.
207
+ const { fields: plainData, transforms } = extractFieldTransforms(data);
208
+ if (transforms.length > 0) {
209
+ return this.addWithTransforms(collectionName, plainData, transforms);
210
+ }
137
211
  const url = this.pathUtil.getCollectionPath(collectionName);
138
212
  const firestoreData = convertToFirestoreDocument(data);
139
213
  if (this.debug) {
@@ -158,6 +232,31 @@ export class FirestoreClient {
158
232
  const result = (await response.json());
159
233
  return convertFromFirestoreDocument(result);
160
234
  }
235
+ /**
236
+ * Create a document that contains field transforms (e.g. serverTimestamp)
237
+ * with an auto-generated ID. Uses the commit endpoint with an
238
+ * `exists: false` precondition and retries on the (astronomically unlikely)
239
+ * ID collision, mirroring the uniqueness of server-generated IDs.
240
+ * @private
241
+ */
242
+ async addWithTransforms(collectionName, plainData, transforms) {
243
+ const fields = convertToFirestoreDocument(plainData).fields;
244
+ const maxAttempts = 5;
245
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
246
+ const documentId = generateAutoId();
247
+ try {
248
+ return await this.commitAndRead(collectionName, documentId, fields, transforms, { exists: false });
249
+ }
250
+ catch (error) {
251
+ const collided = error?.alreadyExists;
252
+ if (collided && attempt < maxAttempts - 1) {
253
+ continue;
254
+ }
255
+ throw error;
256
+ }
257
+ }
258
+ throw new Error("Failed to generate a unique document ID after multiple attempts");
259
+ }
161
260
  /**
162
261
  * Get document
163
262
  * @param collectionName Collection name
@@ -262,6 +361,13 @@ export class FirestoreClient {
262
361
  data = { ...existingDoc, ...data };
263
362
  }
264
363
  }
364
+ // Route writes that contain field transforms (e.g. serverTimestamp)
365
+ // through the commit endpoint; the merged document is sent as the update.
366
+ const { fields: plainData, transforms } = extractFieldTransforms(data);
367
+ if (transforms.length > 0) {
368
+ const fields = convertToFirestoreDocument(plainData).fields;
369
+ return this.commitAndRead(collectionName, documentId, fields, transforms);
370
+ }
265
371
  const firestoreData = convertToFirestoreDocument(data);
266
372
  const headers = await this.prepareHeaders();
267
373
  const response = await fetch(url, {
@@ -465,6 +571,13 @@ export class FirestoreClient {
465
571
  async createWithId(collectionName, documentId, data) {
466
572
  // 操作前に設定をチェック
467
573
  this.checkConfig();
574
+ // Route writes that contain field transforms (e.g. serverTimestamp)
575
+ // through the commit endpoint (which creates the document if absent).
576
+ const { fields: plainData, transforms } = extractFieldTransforms(data);
577
+ if (transforms.length > 0) {
578
+ const fields = convertToFirestoreDocument(plainData).fields;
579
+ return this.commitAndRead(collectionName, documentId, fields, transforms);
580
+ }
468
581
  const url = `${getFirestoreBasePath(this.config.projectId, this.config.databaseId, this.config)}/${collectionName}/${documentId}`;
469
582
  const firestoreData = convertToFirestoreDocument(data);
470
583
  const token = await this.getToken();
@@ -631,13 +744,7 @@ export class CollectionReference {
631
744
  * @returns Random ID
632
745
  */
633
746
  _generateId() {
634
- // Generate 20-character random ID
635
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
636
- let id = "";
637
- for (let i = 0; i < 20; i++) {
638
- id += chars.charAt(Math.floor(Math.random() * chars.length));
639
- }
640
- return id;
747
+ return generateAutoId();
641
748
  }
642
749
  }
643
750
  /**
@@ -661,6 +768,13 @@ export class DocumentReference {
661
768
  get path() {
662
769
  return `${this.collectionPath}/${this.docId}`;
663
770
  }
771
+ /**
772
+ * Fully-qualified Firestore reference value for this document, e.g.
773
+ * `projects/{projectId}/databases/{databaseId}/documents/{path}`.
774
+ */
775
+ get referenceValue() {
776
+ return this.client.getReferenceValue(this.path);
777
+ }
664
778
  /**
665
779
  * Get parent collection reference
666
780
  */
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Sentinel values for special write behaviors, mirroring the native Firebase
3
+ * SDK's `FieldValue`. Currently only `serverTimestamp()` is supported.
4
+ *
5
+ * A `FieldValue` is not a real field value: when used as a field in a write it
6
+ * is translated into a Firestore field transform (applied server-side) rather
7
+ * than serialized as data. Using one anywhere else (e.g. inside an array) is an
8
+ * error.
9
+ */
10
+ export declare class FieldValue {
11
+ readonly methodName: "serverTimestamp";
12
+ private constructor();
13
+ /**
14
+ * Returns a sentinel that sets the field to the server's request timestamp at
15
+ * write time, e.g. `client.add("posts", { createdAt: FieldValue.serverTimestamp() })`.
16
+ */
17
+ static serverTimestamp(): FieldValue;
18
+ /**
19
+ * Whether this sentinel represents the same transform as another.
20
+ */
21
+ isEqual(other: FieldValue): boolean;
22
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Sentinel values for special write behaviors, mirroring the native Firebase
3
+ * SDK's `FieldValue`. Currently only `serverTimestamp()` is supported.
4
+ *
5
+ * A `FieldValue` is not a real field value: when used as a field in a write it
6
+ * is translated into a Firestore field transform (applied server-side) rather
7
+ * than serialized as data. Using one anywhere else (e.g. inside an array) is an
8
+ * error.
9
+ */
10
+ export class FieldValue {
11
+ constructor(methodName) {
12
+ this.methodName = methodName;
13
+ }
14
+ /**
15
+ * Returns a sentinel that sets the field to the server's request timestamp at
16
+ * write time, e.g. `client.add("posts", { createdAt: FieldValue.serverTimestamp() })`.
17
+ */
18
+ static serverTimestamp() {
19
+ return new FieldValue("serverTimestamp");
20
+ }
21
+ /**
22
+ * Whether this sentinel represents the same transform as another.
23
+ */
24
+ isEqual(other) {
25
+ return other instanceof FieldValue && other.methodName === this.methodName;
26
+ }
27
+ }
@@ -1,5 +1,6 @@
1
1
  export * from "./types";
2
2
  import { FirestoreClient, createFirestoreClient, CollectionReference, DocumentReference, CollectionGroup, Query, QuerySnapshot, DocumentSnapshot, WriteResult } from "./client";
3
+ export { FieldValue } from "./field-value";
3
4
  export { getFirestoreToken } from "./utils/auth";
4
5
  export { convertToFirestoreValue, convertFromFirestoreValue, convertToFirestoreDocument, convertFromFirestoreDocument, } from "./utils/converter";
5
6
  export { getFirestoreBasePath, getDocumentId } from "./utils/path";
package/dist/esm/index.js CHANGED
@@ -2,6 +2,8 @@
2
2
  export * from "./types";
3
3
  // クライアントのエクスポート
4
4
  import { FirestoreClient, createFirestoreClient, CollectionReference, DocumentReference, CollectionGroup, Query, QuerySnapshot, DocumentSnapshot, WriteResult, } from "./client";
5
+ // FieldValue センチネルのエクスポート
6
+ export { FieldValue } from "./field-value";
5
7
  // ユーティリティ関数のエクスポート
6
8
  export { getFirestoreToken } from "./utils/auth";
7
9
  export { convertToFirestoreValue, convertFromFirestoreValue, convertToFirestoreDocument, convertFromFirestoreDocument, } from "./utils/converter";
@@ -11,8 +11,63 @@ export interface FirestoreConfig {
11
11
  emulatorHost?: string;
12
12
  emulatorPort?: number;
13
13
  }
14
+ /**
15
+ * A reference to a document. For example: `projects/{project_id}/databases/{database_id}/documents/{document_path}`.
16
+ * Used to represent document references globally and not connected to any particular client.
17
+ */
18
+ export declare class LiteralDocumentReference {
19
+ referenceValue: string;
20
+ constructor(options: Pick<LiteralDocumentReference, "referenceValue">);
21
+ /**
22
+ * Globally unique Firestore document reference paths look like:
23
+ * projects/{project_id}/databases/{database_id}/documents/{document_path}
24
+ * The database id (e.g. `(default)`) never contains a slash, while the
25
+ * document path may contain many. A single anchored regex parses this
26
+ * without pulling in a URLPattern polyfill.
27
+ */
28
+ private static readonly pattern;
29
+ private parse;
30
+ /**
31
+ * Get Project ID
32
+ */
33
+ get project_id(): string;
34
+ /**
35
+ * Get Database ID
36
+ * Ex: `(default)`
37
+ */
38
+ get database_id(): string;
39
+ /**
40
+ * Get document ID
41
+ */
42
+ get id(): string;
43
+ /**
44
+ * Get the collection ID
45
+ */
46
+ get collectionPath(): string;
47
+ /**
48
+ * Get document path
49
+ */
50
+ get path(): string;
51
+ }
52
+ /**
53
+ * A geo point value representing a point on the surface of Earth.
54
+ */
55
+ export declare class LiteralGeoPointValue {
56
+ geoPointValue: {
57
+ /**
58
+ * The latitude in degrees. It must be in the range [-90.0, +90.0].
59
+ */
60
+ latitude: number;
61
+ /**
62
+ * The longitude in degrees. It must be in the range [-180.0, +180.0].
63
+ */
64
+ longitude: number;
65
+ };
66
+ constructor(options: Pick<LiteralGeoPointValue, "geoPointValue">);
67
+ }
14
68
  /**
15
69
  * Firestoreの値型定義
70
+ * See: https://github.com/googleapis/google-api-nodejs-client/blob/5870dfe31f4885eebc82c19f7471c50403308f26/src/apis/firestore/v1.ts#L2246
16
71
  */
17
72
  export type FirestoreFieldValue = {
18
73
  stringValue: string;
@@ -26,7 +81,7 @@ export type FirestoreFieldValue = {
26
81
  nullValue: null;
27
82
  } | {
28
83
  timestampValue: string;
29
- } | {
84
+ } | Pick<LiteralGeoPointValue, 'geoPointValue'> | Pick<LiteralDocumentReference, 'referenceValue'> | {
30
85
  mapValue: {
31
86
  fields: Record<string, FirestoreFieldValue>;
32
87
  };
@@ -35,6 +90,28 @@ export type FirestoreFieldValue = {
35
90
  values: FirestoreFieldValue[];
36
91
  };
37
92
  };
93
+ /**
94
+ * A Firestore field transform applied server-side during a commit write.
95
+ * See: https://firebase.google.com/docs/firestore/reference/rest/v1/Write#FieldTransform
96
+ */
97
+ export interface FieldTransform {
98
+ fieldPath: string;
99
+ setToServerValue: "REQUEST_TIME";
100
+ }
101
+ /**
102
+ * A single write in a `documents:commit` request.
103
+ */
104
+ export interface CommitWrite {
105
+ update: {
106
+ name: string;
107
+ fields: Record<string, FirestoreFieldValue>;
108
+ };
109
+ updateTransforms?: FieldTransform[];
110
+ currentDocument?: {
111
+ exists?: boolean;
112
+ updateTime?: string;
113
+ };
114
+ }
38
115
  /**
39
116
  * Firestoreドキュメント型
40
117
  */
package/dist/esm/types.js CHANGED
@@ -1 +1,70 @@
1
- export {};
1
+ /**
2
+ * A reference to a document. For example: `projects/{project_id}/databases/{database_id}/documents/{document_path}`.
3
+ * Used to represent document references globally and not connected to any particular client.
4
+ */
5
+ export class LiteralDocumentReference {
6
+ constructor(options) {
7
+ this.referenceValue = options.referenceValue;
8
+ }
9
+ parse() {
10
+ const match = LiteralDocumentReference.pattern.exec(this.referenceValue);
11
+ if (!match) {
12
+ throw new Error("Invalid document path. Path does not match pattern.");
13
+ }
14
+ const [, project_id, database_id, document_path] = match;
15
+ return { project_id, database_id, document_path };
16
+ }
17
+ /**
18
+ * Get Project ID
19
+ */
20
+ get project_id() {
21
+ return this.parse().project_id;
22
+ }
23
+ /**
24
+ * Get Database ID
25
+ * Ex: `(default)`
26
+ */
27
+ get database_id() {
28
+ return this.parse().database_id;
29
+ }
30
+ /**
31
+ * Get document ID
32
+ */
33
+ get id() {
34
+ const path = this.parse().document_path;
35
+ const parts = path.split("/");
36
+ const docId = parts[parts.length - 1];
37
+ return docId;
38
+ }
39
+ /**
40
+ * Get the collection ID
41
+ */
42
+ get collectionPath() {
43
+ const path = this.parse().document_path;
44
+ const parts = path.split("/");
45
+ const collectionPath = parts.slice(0, parts.length - 1).join("/");
46
+ return collectionPath;
47
+ }
48
+ /**
49
+ * Get document path
50
+ */
51
+ get path() {
52
+ return this.parse().document_path;
53
+ }
54
+ }
55
+ /**
56
+ * Globally unique Firestore document reference paths look like:
57
+ * projects/{project_id}/databases/{database_id}/documents/{document_path}
58
+ * The database id (e.g. `(default)`) never contains a slash, while the
59
+ * document path may contain many. A single anchored regex parses this
60
+ * without pulling in a URLPattern polyfill.
61
+ */
62
+ LiteralDocumentReference.pattern = /^projects\/([^/]+)\/databases\/([^/]+)\/documents\/(.+)$/;
63
+ /**
64
+ * A geo point value representing a point on the surface of Earth.
65
+ */
66
+ export class LiteralGeoPointValue {
67
+ constructor(options) {
68
+ this.geoPointValue = options.geoPointValue;
69
+ }
70
+ }
@@ -1,4 +1,4 @@
1
- import { FirestoreDocument, FirestoreFieldValue, FirestoreResponse } from "../types";
1
+ import { CommitWrite, FieldTransform, FirestoreDocument, FirestoreFieldValue, FirestoreResponse } from "../types";
2
2
  /**
3
3
  * JSの値をFirestore形式に変換する
4
4
  * @param value 変換する値
@@ -17,6 +17,36 @@ export declare function convertFromFirestoreValue(firestoreValue: FirestoreField
17
17
  * @returns Firestoreドキュメント
18
18
  */
19
19
  export declare function convertToFirestoreDocument(data: Record<string, any>): FirestoreDocument;
20
+ /**
21
+ * Split write data into plain field values and Firestore field transforms.
22
+ *
23
+ * `FieldValue` sentinels (e.g. `serverTimestamp()`) are pulled out into
24
+ * transforms keyed by their (escaped, dot-separated) field path; everything
25
+ * else is left untouched in `fields`. Recursion only descends into plain
26
+ * objects, so class instances (Date / references / geo points) are treated as
27
+ * leaves.
28
+ *
29
+ * @param data Write data (JS values, may contain FieldValue sentinels)
30
+ * @param prefix Field-path prefix used while recursing (internal, pre-escaped)
31
+ */
32
+ export declare function extractFieldTransforms(data: Record<string, any>, prefix?: string): {
33
+ fields: Record<string, any>;
34
+ transforms: FieldTransform[];
35
+ };
36
+ /**
37
+ * Build a single `documents:commit` write that updates a document and applies
38
+ * field transforms. `updateTransforms` / `currentDocument` are only included
39
+ * when relevant.
40
+ *
41
+ * @param documentName Full resource name (projects/.../documents/<path>)
42
+ * @param fields Already-converted Firestore field values
43
+ * @param transforms Field transforms to apply after the update
44
+ * @param currentDocument Optional precondition (e.g. `{ exists: false }`)
45
+ */
46
+ export declare function buildCommitWrite(documentName: string, fields: Record<string, FirestoreFieldValue>, transforms: FieldTransform[], currentDocument?: {
47
+ exists?: boolean;
48
+ updateTime?: string;
49
+ }): CommitWrite;
20
50
  /**
21
51
  * Firestoreドキュメントをオブジェクトに変換
22
52
  * @param doc Firestoreレスポンス