firebase-rest-firestore 1.2.0 → 1.5.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.
Files changed (41) hide show
  1. package/dist/cjs/client.d.ts +417 -0
  2. package/dist/{client.js → cjs/client.js} +442 -279
  3. package/dist/{index.d.ts → cjs/index.d.ts} +1 -1
  4. package/dist/{index.js → cjs/index.js} +1 -2
  5. package/dist/{types.d.ts → cjs/types.d.ts} +4 -0
  6. package/dist/cjs/utils/auth.d.ts +13 -0
  7. package/dist/{utils → cjs/utils}/auth.js +14 -10
  8. package/dist/cjs/utils/config.d.ts +6 -0
  9. package/dist/{utils → cjs/utils}/config.js +0 -12
  10. package/dist/cjs/utils/path.d.ts +73 -0
  11. package/dist/cjs/utils/path.js +176 -0
  12. package/dist/esm/client.d.ts +417 -0
  13. package/dist/esm/client.js +1066 -0
  14. package/dist/esm/index.d.ts +7 -0
  15. package/dist/esm/index.js +11 -0
  16. package/dist/esm/types.d.ts +69 -0
  17. package/dist/esm/types.js +1 -0
  18. package/dist/esm/utils/auth.d.ts +13 -0
  19. package/dist/esm/utils/auth.js +57 -0
  20. package/dist/esm/utils/config.d.ts +6 -0
  21. package/dist/esm/utils/config.js +11 -0
  22. package/dist/esm/utils/converter.d.ts +27 -0
  23. package/dist/esm/utils/converter.js +107 -0
  24. package/dist/esm/utils/path.d.ts +73 -0
  25. package/dist/esm/utils/path.js +169 -0
  26. package/dist/types/client.d.ts +417 -0
  27. package/dist/types/index.d.ts +7 -0
  28. package/dist/types/types.d.ts +69 -0
  29. package/dist/types/utils/auth.d.ts +13 -0
  30. package/dist/types/utils/config.d.ts +6 -0
  31. package/dist/types/utils/converter.d.ts +27 -0
  32. package/dist/types/utils/path.d.ts +73 -0
  33. package/package.json +18 -5
  34. package/dist/client.d.ts +0 -381
  35. package/dist/utils/auth.d.ts +0 -13
  36. package/dist/utils/config.d.ts +0 -13
  37. package/dist/utils/path.d.ts +0 -14
  38. package/dist/utils/path.js +0 -27
  39. /package/dist/{types.js → cjs/types.js} +0 -0
  40. /package/dist/{utils → cjs/utils}/converter.d.ts +0 -0
  41. /package/dist/{utils → cjs/utils}/converter.js +0 -0
@@ -6,24 +6,30 @@ const auth_1 = require("./utils/auth");
6
6
  const converter_1 = require("./utils/converter");
7
7
  const path_1 = require("./utils/path");
8
8
  const config_1 = require("./utils/config");
9
+ const path_2 = require("./utils/path");
9
10
  /**
10
- * Firestoreクライアントクラス
11
+ * Firestore client class
11
12
  */
12
13
  class FirestoreClient {
13
14
  /**
14
- * コンストラクタ
15
- * @param config Firestore設定オブジェクト
15
+ * Constructor
16
+ * @param config Firestore configuration object
16
17
  */
17
18
  constructor(config) {
18
19
  this.token = null;
19
20
  this.tokenExpiry = 0;
20
21
  this.configChecked = false;
22
+ this.debug = false;
21
23
  this.config = config;
22
- // ビルド時にはチェックを行わない
23
- // 実際の操作時に遅延チェックを行う
24
+ this.pathUtil = (0, path_2.createFirestorePath)(config, config.debug || false);
25
+ this.debug = !!config.debug;
26
+ // Log configuration if debug is enabled
27
+ if (this.debug) {
28
+ console.log("Firestore client initialized with config:", JSON.stringify(this.config, null, 2));
29
+ }
24
30
  }
25
31
  /**
26
- * 設定パラメータをチェック
32
+ * Check configuration parameters
27
33
  * @private
28
34
  */
29
35
  checkConfig() {
@@ -31,11 +37,11 @@ class FirestoreClient {
31
37
  return;
32
38
  }
33
39
  // 必須パラメータのチェック
34
- const requiredParams = [
35
- "projectId",
36
- "privateKey",
37
- "clientEmail",
38
- ];
40
+ const requiredParams = ["projectId"];
41
+ // Only require auth parameters when not using emulator
42
+ if (!this.config.useEmulator) {
43
+ requiredParams.push("privateKey", "clientEmail");
44
+ }
39
45
  const missingParams = requiredParams.filter(param => !this.config[param]);
40
46
  if (missingParams.length > 0) {
41
47
  throw new Error(`Missing required Firestore configuration parameters: ${missingParams.join(", ")}`);
@@ -43,14 +49,24 @@ class FirestoreClient {
43
49
  this.configChecked = true;
44
50
  }
45
51
  /**
46
- * 認証トークンを取得(キャッシュあり)
52
+ * Get authentication token (with caching)
47
53
  */
48
54
  async getToken() {
49
- // 操作前に設定をチェック
55
+ // Check settings before operation
50
56
  this.checkConfig();
57
+ // In emulator mode, we don't need a token
58
+ if (this.config.useEmulator) {
59
+ if (this.debug) {
60
+ console.log("Emulator mode: skipping token generation");
61
+ }
62
+ return "emulator-fake-token";
63
+ }
51
64
  const now = Date.now();
52
65
  // トークンが期限切れか未取得の場合は新しく取得
53
66
  if (!this.token || now >= this.tokenExpiry) {
67
+ if (this.debug) {
68
+ console.log("Generating new auth token");
69
+ }
54
70
  this.token = await (0, auth_1.getFirestoreToken)(this.config);
55
71
  // 50分後に期限切れとする(実際は1時間)
56
72
  this.tokenExpiry = now + 50 * 60 * 1000;
@@ -58,23 +74,44 @@ class FirestoreClient {
58
74
  return this.token;
59
75
  }
60
76
  /**
61
- * コレクションリファレンスを取得
62
- * @param path コレクションパス
63
- * @returns CollectionReferenceインスタンス
77
+ * Prepare request headers
78
+ * @param additionalHeaders Additional headers
79
+ * @returns Prepared headers object
80
+ * @private
81
+ */
82
+ async prepareHeaders(additionalHeaders = {}) {
83
+ const headers = {
84
+ "Content-Type": "application/json",
85
+ ...additionalHeaders,
86
+ };
87
+ // Only add auth token for production environment
88
+ if (!this.config.useEmulator) {
89
+ const token = await this.getToken();
90
+ headers["Authorization"] = `Bearer ${token}`;
91
+ }
92
+ else if (this.debug) {
93
+ console.log("Using emulator mode, skipping authorization header");
94
+ }
95
+ return headers;
96
+ }
97
+ /**
98
+ * Get collection reference
99
+ * @param path Collection path
100
+ * @returns CollectionReference instance
64
101
  */
65
102
  collection(path) {
66
- // 設定チェックは実際の操作時に行われる
103
+ // Configuration check is performed at the time of actual operation
67
104
  return new CollectionReference(this, path);
68
105
  }
69
106
  /**
70
- * ドキュメントリファレンスを取得
71
- * @param path ドキュメントパス
72
- * @returns DocumentReferenceインスタンス
107
+ * Get document reference
108
+ * @param path Document path
109
+ * @returns DocumentReference instance
73
110
  */
74
111
  doc(path) {
75
- // 設定チェックは実際の操作時に行われる
112
+ // Configuration check is performed at the time of actual operation
76
113
  const parts = path.split("/");
77
- if (parts.length % 2 === 0) {
114
+ if (parts.length % 2 !== 0) {
78
115
  throw new Error("Invalid document path. Document path must point to a document, not a collection.");
79
116
  }
80
117
  const collectionPath = parts.slice(0, parts.length - 1).join("/");
@@ -82,81 +119,116 @@ class FirestoreClient {
82
119
  return new DocumentReference(this, collectionPath, docId);
83
120
  }
84
121
  /**
85
- * コレクショングループリファレンスを取得
86
- * @param path コレクショングループのID
87
- * @returns CollectionGroupインスタンス
122
+ * Get collection group reference
123
+ * @param path Collection group ID
124
+ * @returns CollectionGroup instance
88
125
  */
89
126
  collectionGroup(path) {
90
127
  return new CollectionGroup(this, path);
91
128
  }
92
129
  /**
93
- * Firestoreにドキュメントを追加
94
- * @param collectionName コレクション名
95
- * @param data 追加するデータ
96
- * @returns 追加されたドキュメント
130
+ * Add document to Firestore
131
+ * @param collectionName Collection name
132
+ * @param data Data to add
133
+ * @returns Added document
97
134
  */
98
135
  async add(collectionName, data) {
99
- // 操作前に設定をチェック
136
+ // Check settings before operation
100
137
  this.checkConfig();
101
- const url = (0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName, this.config.databaseId);
138
+ if (this.debug) {
139
+ console.log(`Adding document to collection: ${collectionName}`, data);
140
+ }
141
+ const url = this.pathUtil.getCollectionPath(collectionName);
102
142
  const firestoreData = (0, converter_1.convertToFirestoreDocument)(data);
103
- const token = await this.getToken();
143
+ if (this.debug) {
144
+ console.log(`Making request to: ${url}`, firestoreData);
145
+ }
146
+ const headers = await this.prepareHeaders();
104
147
  const response = await fetch(url, {
105
148
  method: "POST",
106
- headers: {
107
- "Content-Type": "application/json",
108
- Authorization: `Bearer ${token}`,
109
- },
149
+ headers,
110
150
  body: JSON.stringify(firestoreData),
111
151
  });
152
+ if (this.debug) {
153
+ console.log(`Response status: ${response.status}`);
154
+ }
112
155
  if (!response.ok) {
113
- throw new Error(`Firestore API error: ${response.statusText}`);
156
+ const errorText = await response.text();
157
+ if (this.debug) {
158
+ console.error(`Error response: ${errorText}`);
159
+ }
160
+ throw new Error(`Firestore API error: ${response.statusText || response.status} - ${errorText}`);
114
161
  }
115
162
  const result = (await response.json());
116
163
  return (0, converter_1.convertFromFirestoreDocument)(result);
117
164
  }
118
165
  /**
119
- * ドキュメントを取得
120
- * @param collectionName コレクション名
121
- * @param documentId ドキュメントID
122
- * @returns 取得したドキュメント(存在しない場合はnull)
166
+ * Get document
167
+ * @param collectionName Collection name
168
+ * @param documentId Document ID
169
+ * @returns Retrieved document (null if it doesn't exist)
123
170
  */
124
171
  async get(collectionName, documentId) {
125
- // 操作前に設定をチェック
172
+ // Check settings before operation
126
173
  this.checkConfig();
127
- const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName, this.config.databaseId)}/${documentId}`;
128
- const token = await this.getToken();
129
- const response = await fetch(url, {
130
- method: "GET",
131
- headers: {
132
- Authorization: `Bearer ${token}`,
133
- },
134
- });
135
- if (response.status === 404) {
136
- return null;
174
+ if (this.debug) {
175
+ console.log(`Getting document from collection: ${collectionName}, documentId: ${documentId}`);
137
176
  }
138
- if (!response.ok) {
139
- throw new Error(`Firestore API error: ${response.statusText}`);
177
+ const url = this.pathUtil.getDocumentPath(collectionName, documentId);
178
+ if (this.debug) {
179
+ console.log(`Making request to: ${url}`);
180
+ }
181
+ const headers = await this.prepareHeaders();
182
+ try {
183
+ const response = await fetch(url, {
184
+ method: "GET",
185
+ headers,
186
+ });
187
+ if (this.debug) {
188
+ console.log(`Response status: ${response.status}`);
189
+ }
190
+ // Capture response text for debugging
191
+ const responseText = await response.text();
192
+ if (this.debug) {
193
+ console.log(`Response text: ${responseText.substring(0, 200)}${responseText.length > 200 ? "..." : ""}`);
194
+ }
195
+ if (response.status === 404) {
196
+ return null;
197
+ }
198
+ if (!response.ok) {
199
+ throw new Error(`Firestore API error: ${response.statusText || response.status} - ${responseText}`);
200
+ }
201
+ // Parse the response text
202
+ const result = JSON.parse(responseText);
203
+ return (0, converter_1.convertFromFirestoreDocument)(result);
204
+ }
205
+ catch (error) {
206
+ console.error("Error in get method:", error);
207
+ throw error;
140
208
  }
141
- const result = (await response.json());
142
- return (0, converter_1.convertFromFirestoreDocument)(result);
143
209
  }
144
210
  /**
145
- * ドキュメントを更新
146
- * @param collectionName コレクション名
147
- * @param documentId ドキュメントID
148
- * @param data 更新するデータ
149
- * @returns 更新されたドキュメント
211
+ * Update document
212
+ * @param collectionName Collection name
213
+ * @param documentId Document ID
214
+ * @param data Data to update
215
+ * @returns Updated document
150
216
  */
151
217
  async update(collectionName, documentId, data) {
152
- // 操作前に設定をチェック
218
+ // Check settings before operation
153
219
  this.checkConfig();
154
- const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName, this.config.databaseId)}/${documentId}`;
155
- // 既存のドキュメントを取得してからマージする
220
+ if (this.debug) {
221
+ console.log(`Updating document in collection: ${collectionName}, documentId: ${documentId}`, data);
222
+ }
223
+ const url = this.pathUtil.getDocumentPath(collectionName, documentId);
224
+ if (this.debug) {
225
+ console.log(`Making request to: ${url}`);
226
+ }
227
+ // Get existing document and merge
156
228
  const existingDoc = await this.get(collectionName, documentId);
157
229
  if (existingDoc) {
158
- // ネストしたフィールドの対応
159
- // data内にドット記法のキーがあるかチェック (例: "favorites.color")
230
+ // Check for nested fields
231
+ // Check if data contains dot notation keys (e.g., "favorites.color")
160
232
  const updateData = { ...data };
161
233
  const dotNotationKeys = Object.keys(data).filter(key => key.includes("."));
162
234
  if (dotNotationKeys.length > 0) {
@@ -195,186 +267,263 @@ class FirestoreClient {
195
267
  }
196
268
  }
197
269
  const firestoreData = (0, converter_1.convertToFirestoreDocument)(data);
198
- const token = await this.getToken();
270
+ const headers = await this.prepareHeaders();
199
271
  const response = await fetch(url, {
200
272
  method: "PATCH",
201
- headers: {
202
- "Content-Type": "application/json",
203
- Authorization: `Bearer ${token}`,
204
- },
273
+ headers,
205
274
  body: JSON.stringify(firestoreData),
206
275
  });
276
+ if (this.debug) {
277
+ console.log(`Response status: ${response.status}`);
278
+ }
207
279
  if (!response.ok) {
208
- throw new Error(`Firestore API error: ${response.statusText}`);
280
+ const errorText = await response.text();
281
+ if (this.debug) {
282
+ console.error(`Error response: ${errorText}`);
283
+ }
284
+ throw new Error(`Firestore API error: ${response.statusText || response.status} - ${errorText}`);
209
285
  }
210
286
  const result = (await response.json());
211
287
  return (0, converter_1.convertFromFirestoreDocument)(result);
212
288
  }
213
289
  /**
214
- * ドキュメントを削除
215
- * @param collectionName コレクション名
216
- * @param documentId ドキュメントID
217
- * @returns 削除成功時はtrue
290
+ * Delete document
291
+ * @param collectionName Collection name
292
+ * @param documentId Document ID
293
+ * @returns true if deletion successful
218
294
  */
219
295
  async delete(collectionName, documentId) {
220
- // 操作前に設定をチェック
296
+ // Check settings before operation
221
297
  this.checkConfig();
222
- const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName, this.config.databaseId)}/${documentId}`;
223
- const token = await this.getToken();
298
+ if (this.debug) {
299
+ console.log(`Deleting document from collection: ${collectionName}, documentId: ${documentId}`);
300
+ }
301
+ const url = this.pathUtil.getDocumentPath(collectionName, documentId);
302
+ if (this.debug) {
303
+ console.log(`Making request to: ${url}`);
304
+ }
305
+ // Different header handling for emulator
306
+ const headers = {};
307
+ // Only add auth token for production environment
308
+ if (!this.config.useEmulator) {
309
+ const token = await this.getToken();
310
+ headers["Authorization"] = `Bearer ${token}`;
311
+ }
224
312
  const response = await fetch(url, {
225
313
  method: "DELETE",
226
- headers: {
227
- Authorization: `Bearer ${token}`,
228
- },
314
+ headers,
229
315
  });
316
+ if (this.debug) {
317
+ console.log(`Response status: ${response.status}`);
318
+ }
230
319
  if (!response.ok) {
231
- throw new Error(`Firestore API error: ${response.statusText}`);
320
+ const errorText = await response.text();
321
+ if (this.debug) {
322
+ console.error(`Error response: ${errorText}`);
323
+ }
324
+ throw new Error(`Firestore API error: ${response.statusText || response.status} - ${errorText}`);
232
325
  }
233
326
  return true;
234
327
  }
235
328
  /**
236
- * コレクションのドキュメントを検索
237
- * @param collectionPath コレクションパス
238
- * @param options クエリオプション
239
- * @param allDescendants 子孫コレクションを含むかどうか
240
- * @returns 検索結果のドキュメント配列
329
+ * Query documents in a collection
330
+ * @param collectionPath Collection path
331
+ * @param options Query options
332
+ * @param allDescendants Whether to include descendant collections
333
+ * @returns Array of documents matching the query
241
334
  */
242
- async query(collectionPath, options = {}, allDescendants) {
243
- // 操作前に設定をチェック
335
+ async query(collectionPath, options = {}, allDescendants = false) {
336
+ // Check settings before operation
244
337
  this.checkConfig();
245
- // :runQueryはコレクション単位ではなく、ドキュメントルート単位で実行
246
- // コレクション名パス形式の場合は、endpointをドキュメントルートに置き換える。collectionNameはコレクションIDにする
247
- // e.g) パス形式: collectionPath = "/users/123/items" の場合は、collectionPaths = ["users", "123", "items"], collectionName = "items", basePath += "/users/123"
248
- // e.g) 単一形式: collectionPath = "items" の場合は、collectionPaths = ["items"], collectionName = "items", basePath += ""
249
- const collectionPaths = collectionPath.split("/");
250
- let basePath = `https://firestore.googleapis.com/v1/projects/${this.config.projectId}/databases/${this.config.databaseId || "(default)"}/documents`;
251
- let collectionName = collectionPath;
252
- if (collectionPaths.length > 1) {
253
- basePath = `${basePath}/${collectionPaths.slice(0, -1).join("/")}`;
254
- collectionName = collectionPaths[collectionPaths.length - 1];
255
- }
256
- const url = `${basePath}:runQuery`;
257
- // クエリ構築
258
- //allDescendants: falseの場合は直下のcollectionを参照。trueの場合は子孫コレクションも参照
259
- const structuredQuery = {
260
- from: [
261
- {
262
- collectionId: collectionName,
263
- allDescendants,
338
+ try {
339
+ // Parse the collection path
340
+ const segments = collectionPath.split("/");
341
+ const collectionId = segments[segments.length - 1];
342
+ // Get the proper runQuery URL from our path helper
343
+ const queryUrl = this.pathUtil.getRunQueryPath(collectionPath);
344
+ if (this.debug) {
345
+ console.log(`Executing query on collection: ${collectionPath}`);
346
+ console.log(`Using runQuery URL: ${queryUrl}`);
347
+ }
348
+ // Create the structured query
349
+ const requestBody = {
350
+ structuredQuery: {
351
+ from: [
352
+ {
353
+ collectionId,
354
+ allDescendants,
355
+ },
356
+ ],
264
357
  },
265
- ],
266
- };
267
- // フィルター条件
268
- if (options.where && options.where.length > 0) {
269
- // シンプルなケース: 1つの条件の場合
270
- if (options.where.length === 1) {
271
- const condition = options.where[0];
272
- structuredQuery.where = {
273
- fieldFilter: {
274
- field: { fieldPath: condition.field },
275
- op: condition.op,
276
- value: (0, converter_1.convertToFirestoreValue)(condition.value),
277
- },
358
+ };
359
+ // Add where filters if present
360
+ if (options.where && options.where.length > 0) {
361
+ // Map our operators to Firestore REST API operators
362
+ const opMap = {
363
+ "==": "EQUAL",
364
+ "!=": "NOT_EQUAL",
365
+ "<": "LESS_THAN",
366
+ "<=": "LESS_THAN_OR_EQUAL",
367
+ ">": "GREATER_THAN",
368
+ ">=": "GREATER_THAN_OR_EQUAL",
369
+ "array-contains": "ARRAY_CONTAINS",
370
+ in: "IN",
371
+ "array-contains-any": "ARRAY_CONTAINS_ANY",
372
+ "not-in": "NOT_IN",
278
373
  };
374
+ // Single where clause
375
+ if (options.where.length === 1) {
376
+ const filter = options.where[0];
377
+ const firestoreOp = opMap[filter.op] || filter.op;
378
+ requestBody.structuredQuery.where = {
379
+ fieldFilter: {
380
+ field: { fieldPath: filter.field },
381
+ op: firestoreOp,
382
+ value: (0, converter_1.convertToFirestoreValue)(filter.value),
383
+ },
384
+ };
385
+ }
386
+ // Multiple where clauses (AND)
387
+ else {
388
+ requestBody.structuredQuery.where = {
389
+ compositeFilter: {
390
+ op: "AND",
391
+ filters: options.where.map(filter => {
392
+ const firestoreOp = opMap[filter.op] || filter.op;
393
+ return {
394
+ fieldFilter: {
395
+ field: { fieldPath: filter.field },
396
+ op: firestoreOp,
397
+ value: (0, converter_1.convertToFirestoreValue)(filter.value),
398
+ },
399
+ };
400
+ }),
401
+ },
402
+ };
403
+ }
279
404
  }
280
- else {
281
- // 複数条件の場合
282
- structuredQuery.where = {
283
- compositeFilter: {
284
- op: "AND",
285
- filters: options.where.map(condition => ({
286
- fieldFilter: {
287
- field: { fieldPath: condition.field },
288
- op: condition.op,
289
- value: (0, converter_1.convertToFirestoreValue)(condition.value),
290
- },
291
- })),
405
+ // Add order by if present
406
+ if (options.orderBy) {
407
+ requestBody.structuredQuery.orderBy = [
408
+ {
409
+ field: { fieldPath: options.orderBy },
410
+ direction: options.orderDirection || "ASCENDING",
292
411
  },
293
- };
412
+ ];
294
413
  }
295
- }
296
- // 並べ替え
297
- if (options.orderBy) {
298
- structuredQuery.orderBy = [
299
- {
300
- field: { fieldPath: options.orderBy },
301
- direction: options.orderDirection || "ASCENDING",
302
- },
303
- ];
304
- }
305
- // 制限
306
- if (options.limit) {
307
- structuredQuery.limit = options.limit;
308
- }
309
- // オフセット
310
- if (options.offset) {
311
- structuredQuery.offset = options.offset;
312
- }
313
- const token = await this.getToken();
314
- // 修正: structuredQueryをラップしたリクエストボディを作成
315
- const requestBody = {
316
- structuredQuery: structuredQuery,
317
- };
318
- console.log("クエリリクエスト:", JSON.stringify(requestBody, null, 2));
319
- try {
320
- const response = await fetch(url, {
414
+ // Add limit if present
415
+ if (options.limit) {
416
+ requestBody.structuredQuery.limit = options.limit;
417
+ }
418
+ // Add offset if present
419
+ if (options.offset) {
420
+ requestBody.structuredQuery.offset = options.offset;
421
+ }
422
+ if (this.debug) {
423
+ console.log(`Request payload:`, JSON.stringify(requestBody, null, 2));
424
+ }
425
+ // Use the existing prepareHeaders method for authentication consistency
426
+ const headers = await this.prepareHeaders();
427
+ const response = await fetch(queryUrl, {
321
428
  method: "POST",
322
- headers: {
323
- "Content-Type": "application/json",
324
- Authorization: `Bearer ${token}`,
325
- },
429
+ headers,
326
430
  body: JSON.stringify(requestBody),
327
431
  });
432
+ // Collect response for debugging
328
433
  const responseText = await response.text();
329
- console.log("API レスポンス:", responseText);
434
+ if (this.debug) {
435
+ console.log(`API Response:`, responseText);
436
+ }
330
437
  if (!response.ok) {
331
- throw new Error(`Firestore API error: ${response.statusText} - ${responseText}`);
438
+ throw new Error(`Firestore API error: ${response.status} - ${responseText}`);
332
439
  }
440
+ // Parse the response
333
441
  const results = JSON.parse(responseText);
334
- console.log("変換前の結果:", results);
442
+ if (this.debug) {
443
+ console.log(`Results count: ${results?.length || 0}`);
444
+ }
445
+ // Process the results
446
+ if (!Array.isArray(results)) {
447
+ return [];
448
+ }
335
449
  const convertedResults = results
336
450
  .filter(item => item.document)
337
451
  .map(item => (0, converter_1.convertFromFirestoreDocument)(item.document));
338
- console.log("変換後の結果:", convertedResults);
452
+ if (this.debug) {
453
+ console.log(`Converted results:`, convertedResults);
454
+ }
339
455
  return convertedResults;
340
456
  }
341
457
  catch (error) {
342
- console.error("クエリ実行エラー:", error);
458
+ console.error("Query execution error:", error);
343
459
  throw error;
344
460
  }
345
461
  }
462
+ /**
463
+ * ドキュメントを作成または上書き
464
+ * @param collectionName コレクション名
465
+ * @param documentId ドキュメントID
466
+ * @param data ドキュメントデータ
467
+ * @returns 作成されたドキュメントのリファレンス
468
+ */
469
+ async createWithId(collectionName, documentId, data) {
470
+ // 操作前に設定をチェック
471
+ this.checkConfig();
472
+ const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, this.config.databaseId, this.config)}/${collectionName}/${documentId}`;
473
+ const firestoreData = (0, converter_1.convertToFirestoreDocument)(data);
474
+ const token = await this.getToken();
475
+ const response = await fetch(url, {
476
+ method: "PATCH",
477
+ headers: {
478
+ "Content-Type": "application/json",
479
+ Authorization: `Bearer ${token}`,
480
+ },
481
+ body: JSON.stringify(firestoreData),
482
+ });
483
+ if (!response.ok) {
484
+ throw new Error(`Firestore API error: ${response.statusText}`);
485
+ }
486
+ const result = (await response.json());
487
+ return (0, converter_1.convertFromFirestoreDocument)(result);
488
+ }
346
489
  }
347
490
  exports.FirestoreClient = FirestoreClient;
348
491
  /**
349
- * コレクションリファレンスクラス
492
+ * Collection reference class
350
493
  */
351
494
  class CollectionReference {
352
495
  constructor(client, path) {
353
496
  this.client = client;
354
- this.path = path;
497
+ this._path = path;
355
498
  this._queryConstraints = {
356
499
  where: [],
357
500
  };
358
501
  }
359
502
  /**
360
- * すべての子孫コレクションを含むかどうか
503
+ * Get collection path
504
+ */
505
+ get path() {
506
+ return this._path;
507
+ }
508
+ /**
509
+ * Whether to include all descendant collections
361
510
  */
362
511
  get allDescendants() {
363
512
  return false;
364
513
  }
365
514
  /**
366
- * ドキュメントリファレンスを取得
367
- * @param documentPath ドキュメントID(省略時は自動生成)
368
- * @returns DocumentReferenceインスタンス
515
+ * Get document reference
516
+ * @param documentPath Document ID (auto-generated if omitted)
517
+ * @returns DocumentReference instance
369
518
  */
370
519
  doc(documentPath) {
371
520
  const docId = documentPath || this._generateId();
372
521
  return new DocumentReference(this.client, this.path, docId);
373
522
  }
374
523
  /**
375
- * ドキュメントを追加(IDは自動生成)
376
- * @param data ドキュメントデータ
377
- * @returns 作成されたドキュメントのリファレンス
524
+ * Add document (ID is auto-generated)
525
+ * @param data Document data
526
+ * @returns Reference to the created document
378
527
  */
379
528
  async add(data) {
380
529
  const result = await this.client.add(this.path, data);
@@ -382,17 +531,17 @@ class CollectionReference {
382
531
  return new DocumentReference(this.client, this.path, docId);
383
532
  }
384
533
  /**
385
- * フィルター条件を追加
386
- * @param fieldPath フィールドパス
387
- * @param opStr 演算子
388
- * @param value 値
389
- * @returns Queryインスタンス
534
+ * Add filter condition
535
+ * @param fieldPath Field path
536
+ * @param opStr Operator
537
+ * @param value Value
538
+ * @returns Query instance
390
539
  */
391
540
  where(fieldPath, opStr, value) {
392
541
  const query = new Query(this.client, this.path, {
393
542
  ...this._queryConstraints,
394
543
  }, this.allDescendants);
395
- // 演算子の変換
544
+ // Operator conversion
396
545
  let firestoreOp;
397
546
  switch (opStr) {
398
547
  case "==":
@@ -436,10 +585,10 @@ class CollectionReference {
436
585
  return query;
437
586
  }
438
587
  /**
439
- * 並べ替え条件を追加
440
- * @param fieldPath フィールドパス
441
- * @param directionStr 並べ替え方向('asc'または'desc')
442
- * @returns Queryインスタンス
588
+ * Add sorting condition
589
+ * @param fieldPath Field path
590
+ * @param directionStr Sort direction ('asc' or 'desc')
591
+ * @returns Query instance
443
592
  */
444
593
  orderBy(fieldPath, directionStr = "asc") {
445
594
  const query = new Query(this.client, this.path, {
@@ -451,9 +600,9 @@ class CollectionReference {
451
600
  return query;
452
601
  }
453
602
  /**
454
- * 取得件数の制限を設定
455
- * @param limit 最大件数
456
- * @returns Queryインスタンス
603
+ * Set limit on number of results
604
+ * @param limit Maximum number
605
+ * @returns Query instance
457
606
  */
458
607
  limit(limit) {
459
608
  const query = new Query(this.client, this.path, {
@@ -463,9 +612,9 @@ class CollectionReference {
463
612
  return query;
464
613
  }
465
614
  /**
466
- * スキップ件数を設定
467
- * @param offset スキップ件数
468
- * @returns Queryインスタンス
615
+ * Set number of documents to skip
616
+ * @param offset Number to skip
617
+ * @returns Query instance
469
618
  */
470
619
  offset(offset) {
471
620
  const query = new Query(this.client, this.path, {
@@ -475,19 +624,19 @@ class CollectionReference {
475
624
  return query;
476
625
  }
477
626
  /**
478
- * クエリを実行
479
- * @returns QuerySnapshotインスタンス
627
+ * Execute query
628
+ * @returns QuerySnapshot instance
480
629
  */
481
630
  async get() {
482
631
  const results = await this.client.query(this.path, this._queryConstraints, this.allDescendants);
483
632
  return new QuerySnapshot(results);
484
633
  }
485
634
  /**
486
- * ランダムなIDを生成
487
- * @returns ランダムなID
635
+ * Generate random ID
636
+ * @returns Random ID
488
637
  */
489
638
  _generateId() {
490
- // 20文字のランダムなIDを生成
639
+ // Generate 20-character random ID
491
640
  const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
492
641
  let id = "";
493
642
  for (let i = 0; i < 20; i++) {
@@ -498,7 +647,7 @@ class CollectionReference {
498
647
  }
499
648
  exports.CollectionReference = CollectionReference;
500
649
  /**
501
- * ドキュメントリファレンスクラス
650
+ * Document reference class
502
651
  */
503
652
  class DocumentReference {
504
653
  constructor(client, collectionPath, docId) {
@@ -507,66 +656,71 @@ class DocumentReference {
507
656
  this.docId = docId;
508
657
  }
509
658
  /**
510
- * ドキュメントIDを取得
659
+ * Get document ID
511
660
  */
512
661
  get id() {
513
662
  return this.docId;
514
663
  }
515
664
  /**
516
- * ドキュメントのパスを取得
665
+ * Get document path
517
666
  */
518
667
  get path() {
519
668
  return `${this.collectionPath}/${this.docId}`;
520
669
  }
521
670
  /**
522
- * サブコレクションを取得
523
- * @param collectionPath サブコレクション名
524
- * @returns CollectionReferenceインスタンス
671
+ * Get parent collection reference
672
+ */
673
+ get parent() {
674
+ return new CollectionReference(this.client, this.collectionPath);
675
+ }
676
+ /**
677
+ * Get subcollection
678
+ * @param collectionPath Subcollection name
679
+ * @returns CollectionReference instance
525
680
  */
526
681
  collection(collectionPath) {
527
682
  return new CollectionReference(this.client, `${this.path}/${collectionPath}`);
528
683
  }
529
684
  /**
530
- * ドキュメントを取得
531
- * @returns DocumentSnapshotインスタンス
685
+ * Get document
686
+ * @returns DocumentSnapshot instance
532
687
  */
533
688
  async get() {
534
689
  const data = await this.client.get(this.collectionPath, this.docId);
535
690
  return new DocumentSnapshot(this.docId, data);
536
691
  }
537
692
  /**
538
- * ドキュメントを作成または上書き
539
- * @param data ドキュメントデータ
540
- * @param options オプション(mergeは現在サポートされていません)
541
- * @returns WriteResultインスタンス
693
+ * Create or overwrite document
694
+ * @param data Document data
695
+ * @param options Options (merge is not currently supported)
696
+ * @returns WriteResult instance
542
697
  */
543
698
  async set(data, options) {
544
- // 既存のドキュメントを取得
699
+ // Get existing document
545
700
  const existingDoc = await this.client.get(this.collectionPath, this.docId);
546
701
  if (existingDoc) {
547
- // 既存のドキュメントがある場合は更新
702
+ // If existing document exists, update
548
703
  const mergedData = options?.merge ? { ...existingDoc, ...data } : data;
549
704
  await this.client.update(this.collectionPath, this.docId, mergedData);
550
705
  }
551
706
  else {
552
- // 新規作成
553
- const newData = { ...data, id: this.docId };
554
- await this.client.add(this.collectionPath, newData);
707
+ // New creation
708
+ await this.client.createWithId(this.collectionPath, this.docId, data);
555
709
  }
556
710
  return new WriteResult();
557
711
  }
558
712
  /**
559
- * ドキュメントを更新
560
- * @param data 更新データ
561
- * @returns WriteResultインスタンス
713
+ * Update document
714
+ * @param data Update data
715
+ * @returns WriteResult instance
562
716
  */
563
717
  async update(data) {
564
718
  await this.client.update(this.collectionPath, this.docId, data);
565
719
  return new WriteResult();
566
720
  }
567
721
  /**
568
- * ドキュメントを削除
569
- * @returns WriteResultインスタンス
722
+ * Delete document
723
+ * @returns WriteResult instance
570
724
  */
571
725
  async delete() {
572
726
  await this.client.delete(this.collectionPath, this.docId);
@@ -575,7 +729,7 @@ class DocumentReference {
575
729
  }
576
730
  exports.DocumentReference = DocumentReference;
577
731
  /**
578
- * コレクショングループ
732
+ * Collection group
579
733
  */
580
734
  class CollectionGroup {
581
735
  constructor(client, path) {
@@ -586,23 +740,23 @@ class CollectionGroup {
586
740
  };
587
741
  }
588
742
  /**
589
- * すべての子孫コレクションを含むかどうか
743
+ * Whether to include all descendant collections
590
744
  */
591
745
  get allDescendants() {
592
746
  return true;
593
747
  }
594
748
  /**
595
- * フィルター条件を追加
596
- * @param fieldPath フィールドパス
597
- * @param opStr 演算子
598
- * @param value 値
599
- * @returns Queryインスタンス
749
+ * Add filter condition
750
+ * @param fieldPath Field path
751
+ * @param opStr Operator
752
+ * @param value Value
753
+ * @returns Query instance
600
754
  */
601
755
  where(fieldPath, opStr, value) {
602
756
  const query = new Query(this.client, this.path, {
603
757
  ...this._queryConstraints,
604
758
  }, this.allDescendants);
605
- // 演算子の変換
759
+ // Operator conversion
606
760
  let firestoreOp;
607
761
  switch (opStr) {
608
762
  case "==":
@@ -646,10 +800,10 @@ class CollectionGroup {
646
800
  return query;
647
801
  }
648
802
  /**
649
- * 並べ替え条件を追加
650
- * @param fieldPath フィールドパス
651
- * @param directionStr 並べ替え方向('asc'または'desc')
652
- * @returns Queryインスタンス
803
+ * Add sorting condition
804
+ * @param fieldPath Field path
805
+ * @param directionStr Sort direction ('asc' or 'desc')
806
+ * @returns Query instance
653
807
  */
654
808
  orderBy(fieldPath, directionStr = "asc") {
655
809
  const query = new Query(this.client, this.path, {
@@ -661,9 +815,9 @@ class CollectionGroup {
661
815
  return query;
662
816
  }
663
817
  /**
664
- * 取得件数の制限を設定
665
- * @param limit 最大件数
666
- * @returns Queryインスタンス
818
+ * Set limit on number of results
819
+ * @param limit Maximum number
820
+ * @returns Query instance
667
821
  */
668
822
  limit(limit) {
669
823
  const query = new Query(this.client, this.path, {
@@ -673,9 +827,9 @@ class CollectionGroup {
673
827
  return query;
674
828
  }
675
829
  /**
676
- * スキップ件数を設定
677
- * @param offset スキップ件数
678
- * @returns Queryインスタンス
830
+ * Set number of documents to skip
831
+ * @param offset Number to skip
832
+ * @returns Query instance
679
833
  */
680
834
  offset(offset) {
681
835
  const query = new Query(this.client, this.path, {
@@ -685,8 +839,8 @@ class CollectionGroup {
685
839
  return query;
686
840
  }
687
841
  /**
688
- * クエリを実行
689
- * @returns QuerySnapshotインスタンス
842
+ * Execute query
843
+ * @returns QuerySnapshot instance
690
844
  */
691
845
  async get() {
692
846
  const results = await this.client.query(this.path, this._queryConstraints, this.allDescendants);
@@ -695,7 +849,7 @@ class CollectionGroup {
695
849
  }
696
850
  exports.CollectionGroup = CollectionGroup;
697
851
  /**
698
- * クエリクラス
852
+ * Query class
699
853
  */
700
854
  class Query {
701
855
  constructor(client, collectionPath, constraints, allDescendants) {
@@ -705,17 +859,17 @@ class Query {
705
859
  this.allDescendants = allDescendants;
706
860
  }
707
861
  /**
708
- * フィルター条件を追加
709
- * @param fieldPath フィールドパス
710
- * @param opStr 演算子
711
- * @param value 値
712
- * @returns Queryインスタンス
862
+ * Add filter condition
863
+ * @param fieldPath Field path
864
+ * @param opStr Operator
865
+ * @param value Value
866
+ * @returns Query instance
713
867
  */
714
868
  where(fieldPath, opStr, value) {
715
869
  const query = new Query(this.client, this.collectionPath, {
716
870
  ...this._queryConstraints,
717
871
  }, this.allDescendants);
718
- // 演算子の変換
872
+ // Operator conversion
719
873
  let firestoreOp;
720
874
  switch (opStr) {
721
875
  case "==":
@@ -759,10 +913,10 @@ class Query {
759
913
  return query;
760
914
  }
761
915
  /**
762
- * 並べ替え条件を追加
763
- * @param fieldPath フィールドパス
764
- * @param directionStr 並べ替え方向('asc'または'desc')
765
- * @returns Queryインスタンス
916
+ * Add sorting condition
917
+ * @param fieldPath Field path
918
+ * @param directionStr Sort direction ('asc' or 'desc')
919
+ * @returns Query instance
766
920
  */
767
921
  orderBy(fieldPath, directionStr = "asc") {
768
922
  const query = new Query(this.client, this.collectionPath, {
@@ -774,9 +928,9 @@ class Query {
774
928
  return query;
775
929
  }
776
930
  /**
777
- * 取得件数の制限を設定
778
- * @param limit 最大件数
779
- * @returns Queryインスタンス
931
+ * Set limit on number of results
932
+ * @param limit Maximum number
933
+ * @returns Query instance
780
934
  */
781
935
  limit(limit) {
782
936
  const query = new Query(this.client, this.collectionPath, {
@@ -786,9 +940,9 @@ class Query {
786
940
  return query;
787
941
  }
788
942
  /**
789
- * スキップ件数を設定
790
- * @param offset スキップ件数
791
- * @returns Queryインスタンス
943
+ * Set number of documents to skip
944
+ * @param offset Number to skip
945
+ * @returns Query instance
792
946
  */
793
947
  offset(offset) {
794
948
  const query = new Query(this.client, this.collectionPath, {
@@ -798,8 +952,8 @@ class Query {
798
952
  return query;
799
953
  }
800
954
  /**
801
- * クエリを実行
802
- * @returns QuerySnapshotインスタンス
955
+ * Execute query
956
+ * @returns QuerySnapshot instance
803
957
  */
804
958
  async get() {
805
959
  const results = await this.client.query(this.collectionPath, this._queryConstraints, this.allDescendants);
@@ -808,7 +962,7 @@ class Query {
808
962
  }
809
963
  exports.Query = Query;
810
964
  /**
811
- * クエリ結果クラス
965
+ * Query result class
812
966
  */
813
967
  class QuerySnapshot {
814
968
  constructor(results) {
@@ -818,26 +972,26 @@ class QuerySnapshot {
818
972
  });
819
973
  }
820
974
  /**
821
- * 結果のドキュメント配列
975
+ * Array of documents in the result
822
976
  */
823
977
  get docs() {
824
978
  return this._docs;
825
979
  }
826
980
  /**
827
- * 結果が空かどうか
981
+ * Whether the result is empty
828
982
  */
829
983
  get empty() {
830
984
  return this._docs.length === 0;
831
985
  }
832
986
  /**
833
- * 結果の件数
987
+ * Number of results
834
988
  */
835
989
  get size() {
836
990
  return this._docs.length;
837
991
  }
838
992
  /**
839
- * 各ドキュメントに対してコールバックを実行
840
- * @param callback 各ドキュメントに対して実行するコールバック関数
993
+ * Execute callback for each document
994
+ * @param callback Callback function to execute for each document
841
995
  */
842
996
  forEach(callback) {
843
997
  this._docs.forEach(callback);
@@ -845,7 +999,7 @@ class QuerySnapshot {
845
999
  }
846
1000
  exports.QuerySnapshot = QuerySnapshot;
847
1001
  /**
848
- * ドキュメントスナップショットクラス
1002
+ * Document snapshot class
849
1003
  */
850
1004
  class DocumentSnapshot {
851
1005
  constructor(id, data) {
@@ -853,20 +1007,20 @@ class DocumentSnapshot {
853
1007
  this._data = data;
854
1008
  }
855
1009
  /**
856
- * ドキュメントID
1010
+ * Document ID
857
1011
  */
858
1012
  get id() {
859
1013
  return this._id;
860
1014
  }
861
1015
  /**
862
- * ドキュメントが存在するかどうか
1016
+ * Whether the document exists
863
1017
  */
864
1018
  get exists() {
865
1019
  return this._data !== null;
866
1020
  }
867
1021
  /**
868
- * ドキュメントデータを取得
869
- * @returns ドキュメントデータ(存在しない場合はundefined)
1022
+ * Get document data
1023
+ * @returns Document data (undefined if it doesn't exist)
870
1024
  */
871
1025
  data() {
872
1026
  return this._data || undefined;
@@ -874,7 +1028,7 @@ class DocumentSnapshot {
874
1028
  }
875
1029
  exports.DocumentSnapshot = DocumentSnapshot;
876
1030
  /**
877
- * 書き込み結果クラス
1031
+ * Write result class
878
1032
  */
879
1033
  class WriteResult {
880
1034
  constructor() {
@@ -883,28 +1037,37 @@ class WriteResult {
883
1037
  }
884
1038
  exports.WriteResult = WriteResult;
885
1039
  /**
886
- * 新しいFirestoreクライアントインスタンスを作成
887
- * @param config Firestore設定オブジェクト
888
- * @returns FirestoreClientインスタンス
1040
+ * Create a new Firestore client instance
1041
+ * @param config Firestore configuration object
1042
+ * @returns FirestoreClient instance
889
1043
  *
890
1044
  * @example
891
- * // デフォルトデータベースに接続
1045
+ * // Connect to default database
892
1046
  * const db = createFirestoreClient({
893
1047
  * projectId: 'your-project-id',
894
1048
  * privateKey: 'your-private-key',
895
1049
  * clientEmail: 'your-client-email'
896
1050
  * });
897
1051
  *
898
- * // 異なる名前のデータベースに接続
1052
+ * // Connect to a different named database
899
1053
  * const customDb = createFirestoreClient({
900
1054
  * projectId: 'your-project-id',
901
1055
  * privateKey: 'your-private-key',
902
1056
  * clientEmail: 'your-client-email',
903
1057
  * databaseId: 'your-database-id'
904
1058
  * });
1059
+ *
1060
+ * // Connect to local emulator (no auth required)
1061
+ * const emulatorDb = createFirestoreClient({
1062
+ * projectId: 'demo-project',
1063
+ * useEmulator: true,
1064
+ * emulatorHost: '127.0.',
1065
+ * emulatorPort: 8080,
1066
+ * debug: true // Optional: enables detailed logging
1067
+ * });
905
1068
  */
906
1069
  function createFirestoreClient(config) {
907
- // 秘密鍵のフォーマットを確認
1070
+ // Check private key format
908
1071
  if (config.privateKey) {
909
1072
  config = {
910
1073
  ...config,