firebase-rest-firestore 0.1.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.
- package/README.md +431 -0
- package/dist/cjs/index.d.ts +59 -0
- package/dist/cjs/index.js +388 -0
- package/dist/client.d.ts +68 -0
- package/dist/client.js +209 -0
- package/dist/esm/index.d.ts +59 -0
- package/dist/esm/index.js +349 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +34 -0
- package/dist/types.d.ts +63 -0
- package/dist/types.js +2 -0
- package/dist/utils/auth.d.ts +13 -0
- package/dist/utils/auth.js +90 -0
- package/dist/utils/converter.d.ts +27 -0
- package/dist/utils/converter.js +113 -0
- package/dist/utils/path.d.ts +13 -0
- package/dist/utils/path.js +25 -0
- package/package.json +46 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.firestore = exports.FirestoreClient = void 0;
|
|
37
|
+
exports.getFirestoreToken = getFirestoreToken;
|
|
38
|
+
exports.saveToFirestore = saveToFirestore;
|
|
39
|
+
const jose = __importStar(require("jose"));
|
|
40
|
+
// Firestore関連の定数
|
|
41
|
+
const FIREBASE_PROJECT_ID = process.env.FIREBASE_PROJECT_ID;
|
|
42
|
+
const FIREBASE_PRIVATE_KEY_ID = process.env.FIREBASE_PRIVATE_KEY_ID;
|
|
43
|
+
const FIREBASE_PRIVATE_KEY = process.env.FIREBASE_PRIVATE_KEY?.replace(/\\n/g, "\n");
|
|
44
|
+
const FIREBASE_CLIENT_EMAIL = process.env.FIREBASE_CLIENT_EMAIL;
|
|
45
|
+
const FIREBASE_CLIENT_ID = process.env.FIREBASE_CLIENT_ID;
|
|
46
|
+
const FIREBASE_CLIENT_CERT_URL = process.env.FIREBASE_CLIENT_CERT_URL;
|
|
47
|
+
// サービスアカウント情報
|
|
48
|
+
const serviceAccount = {
|
|
49
|
+
type: "service_account",
|
|
50
|
+
project_id: FIREBASE_PROJECT_ID,
|
|
51
|
+
private_key_id: FIREBASE_PRIVATE_KEY_ID,
|
|
52
|
+
private_key: FIREBASE_PRIVATE_KEY,
|
|
53
|
+
client_email: FIREBASE_CLIENT_EMAIL,
|
|
54
|
+
client_id: FIREBASE_CLIENT_ID,
|
|
55
|
+
auth_uri: "https://accounts.google.com/o/oauth2/auth",
|
|
56
|
+
token_uri: "https://oauth2.googleapis.com/token",
|
|
57
|
+
auth_provider_x509_cert_url: "https://www.googleapis.com/oauth2/v1/certs",
|
|
58
|
+
client_x509_cert_url: FIREBASE_CLIENT_CERT_URL,
|
|
59
|
+
};
|
|
60
|
+
// Firestoreのベースパス
|
|
61
|
+
const getFirestoreBasePath = (collectionName) => {
|
|
62
|
+
const basePath = `https://firestore.googleapis.com/v1/projects/${FIREBASE_PROJECT_ID}/databases/(default)/documents`;
|
|
63
|
+
return collectionName ? `${basePath}/${collectionName}` : basePath;
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* JSの値をFirestore形式に変換する
|
|
67
|
+
*/
|
|
68
|
+
function convertToFirestoreValue(value) {
|
|
69
|
+
if (value instanceof Date) {
|
|
70
|
+
return { timestampValue: value.toISOString() };
|
|
71
|
+
}
|
|
72
|
+
else if (typeof value === "string") {
|
|
73
|
+
return { stringValue: value };
|
|
74
|
+
}
|
|
75
|
+
else if (typeof value === "number") {
|
|
76
|
+
return Number.isInteger(value)
|
|
77
|
+
? { integerValue: value }
|
|
78
|
+
: { doubleValue: value };
|
|
79
|
+
}
|
|
80
|
+
else if (typeof value === "boolean") {
|
|
81
|
+
return { booleanValue: value };
|
|
82
|
+
}
|
|
83
|
+
else if (value === null || value === undefined) {
|
|
84
|
+
return { nullValue: null };
|
|
85
|
+
}
|
|
86
|
+
else if (Array.isArray(value)) {
|
|
87
|
+
return {
|
|
88
|
+
arrayValue: {
|
|
89
|
+
values: value.map(item => convertToFirestoreValue(item)),
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
else if (typeof value === "object") {
|
|
94
|
+
const fields = Object.entries(value).reduce((acc, [key, val]) => ({
|
|
95
|
+
...acc,
|
|
96
|
+
[key]: convertToFirestoreValue(val),
|
|
97
|
+
}), {});
|
|
98
|
+
return { mapValue: { fields } };
|
|
99
|
+
}
|
|
100
|
+
// デフォルトは文字列化
|
|
101
|
+
return { stringValue: String(value) };
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Firestore形式からJSの値に変換する
|
|
105
|
+
*/
|
|
106
|
+
function convertFromFirestoreValue(firestoreValue) {
|
|
107
|
+
if ("stringValue" in firestoreValue) {
|
|
108
|
+
return firestoreValue.stringValue;
|
|
109
|
+
}
|
|
110
|
+
else if ("integerValue" in firestoreValue) {
|
|
111
|
+
return Number(firestoreValue.integerValue);
|
|
112
|
+
}
|
|
113
|
+
else if ("doubleValue" in firestoreValue) {
|
|
114
|
+
return firestoreValue.doubleValue;
|
|
115
|
+
}
|
|
116
|
+
else if ("booleanValue" in firestoreValue) {
|
|
117
|
+
return firestoreValue.booleanValue;
|
|
118
|
+
}
|
|
119
|
+
else if ("nullValue" in firestoreValue) {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
else if ("timestampValue" in firestoreValue) {
|
|
123
|
+
return new Date(firestoreValue.timestampValue);
|
|
124
|
+
}
|
|
125
|
+
else if ("mapValue" in firestoreValue && firestoreValue.mapValue.fields) {
|
|
126
|
+
return Object.entries(firestoreValue.mapValue.fields).reduce((acc, [key, val]) => ({
|
|
127
|
+
...acc,
|
|
128
|
+
[key]: convertFromFirestoreValue(val),
|
|
129
|
+
}), {});
|
|
130
|
+
}
|
|
131
|
+
else if ("arrayValue" in firestoreValue &&
|
|
132
|
+
firestoreValue.arrayValue.values) {
|
|
133
|
+
return firestoreValue.arrayValue.values.map(convertFromFirestoreValue);
|
|
134
|
+
}
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* オブジェクトをFirestoreドキュメント形式に変換
|
|
139
|
+
*/
|
|
140
|
+
function convertToFirestoreDocument(data) {
|
|
141
|
+
return {
|
|
142
|
+
fields: Object.entries(data).reduce((acc, [key, value]) => ({
|
|
143
|
+
...acc,
|
|
144
|
+
[key]: convertToFirestoreValue(value),
|
|
145
|
+
}), {}),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Firestoreドキュメントをオブジェクトに変換
|
|
150
|
+
*/
|
|
151
|
+
function convertFromFirestoreDocument(doc) {
|
|
152
|
+
if (!doc.fields)
|
|
153
|
+
return { id: getDocumentId(doc.name) };
|
|
154
|
+
const result = Object.entries(doc.fields).reduce((acc, [key, value]) => ({
|
|
155
|
+
...acc,
|
|
156
|
+
[key]: convertFromFirestoreValue(value),
|
|
157
|
+
}), {});
|
|
158
|
+
return {
|
|
159
|
+
...result,
|
|
160
|
+
id: getDocumentId(doc.name),
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* ドキュメントパスからIDを抽出
|
|
165
|
+
*/
|
|
166
|
+
function getDocumentId(path) {
|
|
167
|
+
if (!path)
|
|
168
|
+
return "";
|
|
169
|
+
const pathParts = path.split("/");
|
|
170
|
+
return pathParts[pathParts.length - 1];
|
|
171
|
+
}
|
|
172
|
+
// JWT(JSON Web Token)を作成する関数
|
|
173
|
+
async function createJWT(serviceAccount) {
|
|
174
|
+
const now = Math.floor(Date.now() / 1000);
|
|
175
|
+
const payload = {
|
|
176
|
+
iss: serviceAccount.client_email,
|
|
177
|
+
sub: serviceAccount.client_email,
|
|
178
|
+
aud: "https://oauth2.googleapis.com/token",
|
|
179
|
+
iat: now,
|
|
180
|
+
exp: now + 3600, // 1時間後に期限切れ
|
|
181
|
+
scope: "https://www.googleapis.com/auth/datastore",
|
|
182
|
+
};
|
|
183
|
+
try {
|
|
184
|
+
// 秘密鍵をインポート
|
|
185
|
+
const privateKey = await jose.importPKCS8(serviceAccount.private_key, "RS256");
|
|
186
|
+
// JWTを作成
|
|
187
|
+
const token = await new jose.SignJWT(payload)
|
|
188
|
+
.setProtectedHeader({
|
|
189
|
+
alg: "RS256",
|
|
190
|
+
kid: serviceAccount.private_key_id,
|
|
191
|
+
typ: "JWT",
|
|
192
|
+
})
|
|
193
|
+
.sign(privateKey);
|
|
194
|
+
return token;
|
|
195
|
+
}
|
|
196
|
+
catch (error) {
|
|
197
|
+
console.error("JWT作成エラー:", error);
|
|
198
|
+
throw error;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Firestoreの認証トークンを取得する関数
|
|
203
|
+
*/
|
|
204
|
+
async function getFirestoreToken() {
|
|
205
|
+
// トークンを取得するためのリクエスト
|
|
206
|
+
const response = await fetch("https://oauth2.googleapis.com/token", {
|
|
207
|
+
method: "POST",
|
|
208
|
+
headers: {
|
|
209
|
+
"Content-Type": "application/json",
|
|
210
|
+
},
|
|
211
|
+
body: JSON.stringify({
|
|
212
|
+
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
|
|
213
|
+
assertion: await createJWT(serviceAccount),
|
|
214
|
+
}),
|
|
215
|
+
});
|
|
216
|
+
const data = (await response.json());
|
|
217
|
+
return data.access_token;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Firestoreクライアントクラス
|
|
221
|
+
*/
|
|
222
|
+
class FirestoreClient {
|
|
223
|
+
constructor() {
|
|
224
|
+
this.token = null;
|
|
225
|
+
this.tokenExpiry = 0;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* 認証トークンを取得(キャッシュあり)
|
|
229
|
+
*/
|
|
230
|
+
async getToken() {
|
|
231
|
+
const now = Date.now();
|
|
232
|
+
// トークンが期限切れか未取得の場合は新しく取得
|
|
233
|
+
if (!this.token || now >= this.tokenExpiry) {
|
|
234
|
+
this.token = await getFirestoreToken();
|
|
235
|
+
// 50分後に期限切れとする(実際は1時間)
|
|
236
|
+
this.tokenExpiry = now + 50 * 60 * 1000;
|
|
237
|
+
}
|
|
238
|
+
return this.token;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Firestoreにドキュメントを作成
|
|
242
|
+
*/
|
|
243
|
+
async create(collectionName, data) {
|
|
244
|
+
const url = getFirestoreBasePath(collectionName);
|
|
245
|
+
const firestoreData = convertToFirestoreDocument(data);
|
|
246
|
+
const token = await this.getToken();
|
|
247
|
+
const response = await fetch(url, {
|
|
248
|
+
method: "POST",
|
|
249
|
+
headers: {
|
|
250
|
+
"Content-Type": "application/json",
|
|
251
|
+
Authorization: `Bearer ${token}`,
|
|
252
|
+
},
|
|
253
|
+
body: JSON.stringify(firestoreData),
|
|
254
|
+
});
|
|
255
|
+
if (!response.ok) {
|
|
256
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
257
|
+
}
|
|
258
|
+
const result = (await response.json());
|
|
259
|
+
return convertFromFirestoreDocument(result);
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* ドキュメントを取得
|
|
263
|
+
*/
|
|
264
|
+
async get(collectionName, documentId) {
|
|
265
|
+
const url = `${getFirestoreBasePath(collectionName)}/${documentId}`;
|
|
266
|
+
const token = await this.getToken();
|
|
267
|
+
const response = await fetch(url, {
|
|
268
|
+
method: "GET",
|
|
269
|
+
headers: {
|
|
270
|
+
Authorization: `Bearer ${token}`,
|
|
271
|
+
},
|
|
272
|
+
});
|
|
273
|
+
if (response.status === 404) {
|
|
274
|
+
return null;
|
|
275
|
+
}
|
|
276
|
+
if (!response.ok) {
|
|
277
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
278
|
+
}
|
|
279
|
+
const result = (await response.json());
|
|
280
|
+
return convertFromFirestoreDocument(result);
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* ドキュメントを更新
|
|
284
|
+
*/
|
|
285
|
+
async update(collectionName, documentId, data) {
|
|
286
|
+
const url = `${getFirestoreBasePath(collectionName)}/${documentId}`;
|
|
287
|
+
const firestoreData = convertToFirestoreDocument(data);
|
|
288
|
+
const token = await this.getToken();
|
|
289
|
+
const response = await fetch(url, {
|
|
290
|
+
method: "PATCH",
|
|
291
|
+
headers: {
|
|
292
|
+
"Content-Type": "application/json",
|
|
293
|
+
Authorization: `Bearer ${token}`,
|
|
294
|
+
},
|
|
295
|
+
body: JSON.stringify(firestoreData),
|
|
296
|
+
});
|
|
297
|
+
if (!response.ok) {
|
|
298
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
299
|
+
}
|
|
300
|
+
const result = (await response.json());
|
|
301
|
+
return convertFromFirestoreDocument(result);
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* ドキュメントを削除
|
|
305
|
+
*/
|
|
306
|
+
async delete(collectionName, documentId) {
|
|
307
|
+
const url = `${getFirestoreBasePath(collectionName)}/${documentId}`;
|
|
308
|
+
const token = await this.getToken();
|
|
309
|
+
const response = await fetch(url, {
|
|
310
|
+
method: "DELETE",
|
|
311
|
+
headers: {
|
|
312
|
+
Authorization: `Bearer ${token}`,
|
|
313
|
+
},
|
|
314
|
+
});
|
|
315
|
+
if (!response.ok) {
|
|
316
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
317
|
+
}
|
|
318
|
+
return true;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* コレクションのドキュメントを検索
|
|
322
|
+
*/
|
|
323
|
+
async query(collectionName, options = {}) {
|
|
324
|
+
const url = `${getFirestoreBasePath(collectionName)}:runQuery`;
|
|
325
|
+
// クエリ構築
|
|
326
|
+
const structuredQuery = {
|
|
327
|
+
from: [{ collectionId: collectionName }],
|
|
328
|
+
};
|
|
329
|
+
// フィルター条件
|
|
330
|
+
if (options.where && options.where.length > 0) {
|
|
331
|
+
structuredQuery.where = {
|
|
332
|
+
compositeFilter: {
|
|
333
|
+
op: "AND",
|
|
334
|
+
filters: options.where.map(condition => ({
|
|
335
|
+
fieldFilter: {
|
|
336
|
+
field: { fieldPath: condition.field },
|
|
337
|
+
op: condition.op,
|
|
338
|
+
value: convertToFirestoreValue(condition.value),
|
|
339
|
+
},
|
|
340
|
+
})),
|
|
341
|
+
},
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
// 並べ替え
|
|
345
|
+
if (options.orderBy) {
|
|
346
|
+
structuredQuery.orderBy = [
|
|
347
|
+
{
|
|
348
|
+
field: { fieldPath: options.orderBy },
|
|
349
|
+
direction: "ASCENDING",
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
}
|
|
353
|
+
// 制限
|
|
354
|
+
if (options.limit) {
|
|
355
|
+
structuredQuery.limit = options.limit;
|
|
356
|
+
}
|
|
357
|
+
// オフセット
|
|
358
|
+
if (options.offset) {
|
|
359
|
+
structuredQuery.offset = options.offset;
|
|
360
|
+
}
|
|
361
|
+
const token = await this.getToken();
|
|
362
|
+
const response = await fetch(url, {
|
|
363
|
+
method: "POST",
|
|
364
|
+
headers: {
|
|
365
|
+
"Content-Type": "application/json",
|
|
366
|
+
Authorization: `Bearer ${token}`,
|
|
367
|
+
},
|
|
368
|
+
body: JSON.stringify({
|
|
369
|
+
structuredQuery,
|
|
370
|
+
}),
|
|
371
|
+
});
|
|
372
|
+
if (!response.ok) {
|
|
373
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
374
|
+
}
|
|
375
|
+
const results = (await response.json());
|
|
376
|
+
return results
|
|
377
|
+
.filter(item => item.document)
|
|
378
|
+
.map(item => convertFromFirestoreDocument(item.document));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
exports.FirestoreClient = FirestoreClient;
|
|
382
|
+
// デフォルトのFirestoreクライアントインスタンス
|
|
383
|
+
exports.firestore = new FirestoreClient();
|
|
384
|
+
// 後方互換性のための関数
|
|
385
|
+
async function saveToFirestore(collectionName, data) {
|
|
386
|
+
const result = await exports.firestore.create(collectionName, data);
|
|
387
|
+
return { documentId: result.id, result };
|
|
388
|
+
}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { FirestoreConfig, QueryOptions } from "./types";
|
|
2
|
+
/**
|
|
3
|
+
* Firestoreクライアントクラス
|
|
4
|
+
*/
|
|
5
|
+
export declare class FirestoreClient {
|
|
6
|
+
private token;
|
|
7
|
+
private tokenExpiry;
|
|
8
|
+
private config;
|
|
9
|
+
/**
|
|
10
|
+
* コンストラクタ
|
|
11
|
+
* @param config Firestore設定オブジェクト
|
|
12
|
+
*/
|
|
13
|
+
constructor(config: FirestoreConfig);
|
|
14
|
+
/**
|
|
15
|
+
* 認証トークンを取得(キャッシュあり)
|
|
16
|
+
*/
|
|
17
|
+
private getToken;
|
|
18
|
+
/**
|
|
19
|
+
* Firestoreにドキュメントを作成
|
|
20
|
+
* @param collectionName コレクション名
|
|
21
|
+
* @param data 作成するデータ
|
|
22
|
+
* @returns 作成されたドキュメント
|
|
23
|
+
*/
|
|
24
|
+
create(collectionName: string, data: Record<string, any>): Promise<Record<string, any> & {
|
|
25
|
+
id: string;
|
|
26
|
+
}>;
|
|
27
|
+
/**
|
|
28
|
+
* ドキュメントを取得
|
|
29
|
+
* @param collectionName コレクション名
|
|
30
|
+
* @param documentId ドキュメントID
|
|
31
|
+
* @returns 取得したドキュメント(存在しない場合はnull)
|
|
32
|
+
*/
|
|
33
|
+
get(collectionName: string, documentId: string): Promise<(Record<string, any> & {
|
|
34
|
+
id: string;
|
|
35
|
+
}) | null>;
|
|
36
|
+
/**
|
|
37
|
+
* ドキュメントを更新
|
|
38
|
+
* @param collectionName コレクション名
|
|
39
|
+
* @param documentId ドキュメントID
|
|
40
|
+
* @param data 更新するデータ
|
|
41
|
+
* @returns 更新されたドキュメント
|
|
42
|
+
*/
|
|
43
|
+
update(collectionName: string, documentId: string, data: Record<string, any>): Promise<Record<string, any> & {
|
|
44
|
+
id: string;
|
|
45
|
+
}>;
|
|
46
|
+
/**
|
|
47
|
+
* ドキュメントを削除
|
|
48
|
+
* @param collectionName コレクション名
|
|
49
|
+
* @param documentId ドキュメントID
|
|
50
|
+
* @returns 削除成功時はtrue
|
|
51
|
+
*/
|
|
52
|
+
delete(collectionName: string, documentId: string): Promise<boolean>;
|
|
53
|
+
/**
|
|
54
|
+
* コレクションのドキュメントを検索
|
|
55
|
+
* @param collectionName コレクション名
|
|
56
|
+
* @param options クエリオプション
|
|
57
|
+
* @returns 検索結果のドキュメント配列
|
|
58
|
+
*/
|
|
59
|
+
query(collectionName: string, options?: QueryOptions): Promise<(Record<string, any> & {
|
|
60
|
+
id: string;
|
|
61
|
+
})[]>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* 新しいFirestoreクライアントインスタンスを作成
|
|
65
|
+
* @param config Firestore設定オブジェクト
|
|
66
|
+
* @returns FirestoreClientインスタンス
|
|
67
|
+
*/
|
|
68
|
+
export declare function createFirestoreClient(config: FirestoreConfig): FirestoreClient;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.FirestoreClient = void 0;
|
|
4
|
+
exports.createFirestoreClient = createFirestoreClient;
|
|
5
|
+
const auth_1 = require("./utils/auth");
|
|
6
|
+
const converter_1 = require("./utils/converter");
|
|
7
|
+
const path_1 = require("./utils/path");
|
|
8
|
+
/**
|
|
9
|
+
* Firestoreクライアントクラス
|
|
10
|
+
*/
|
|
11
|
+
class FirestoreClient {
|
|
12
|
+
/**
|
|
13
|
+
* コンストラクタ
|
|
14
|
+
* @param config Firestore設定オブジェクト
|
|
15
|
+
*/
|
|
16
|
+
constructor(config) {
|
|
17
|
+
this.token = null;
|
|
18
|
+
this.tokenExpiry = 0;
|
|
19
|
+
this.config = config;
|
|
20
|
+
// 必須パラメータのチェック
|
|
21
|
+
const requiredParams = [
|
|
22
|
+
"projectId",
|
|
23
|
+
"privateKey",
|
|
24
|
+
"clientEmail",
|
|
25
|
+
];
|
|
26
|
+
const missingParams = requiredParams.filter(param => !this.config[param]);
|
|
27
|
+
if (missingParams.length > 0) {
|
|
28
|
+
throw new Error(`Missing required Firestore configuration parameters: ${missingParams.join(", ")}`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* 認証トークンを取得(キャッシュあり)
|
|
33
|
+
*/
|
|
34
|
+
async getToken() {
|
|
35
|
+
const now = Date.now();
|
|
36
|
+
// トークンが期限切れか未取得の場合は新しく取得
|
|
37
|
+
if (!this.token || now >= this.tokenExpiry) {
|
|
38
|
+
this.token = await (0, auth_1.getFirestoreToken)(this.config);
|
|
39
|
+
// 50分後に期限切れとする(実際は1時間)
|
|
40
|
+
this.tokenExpiry = now + 50 * 60 * 1000;
|
|
41
|
+
}
|
|
42
|
+
return this.token;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Firestoreにドキュメントを作成
|
|
46
|
+
* @param collectionName コレクション名
|
|
47
|
+
* @param data 作成するデータ
|
|
48
|
+
* @returns 作成されたドキュメント
|
|
49
|
+
*/
|
|
50
|
+
async create(collectionName, data) {
|
|
51
|
+
const url = (0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName);
|
|
52
|
+
const firestoreData = (0, converter_1.convertToFirestoreDocument)(data);
|
|
53
|
+
const token = await this.getToken();
|
|
54
|
+
const response = await fetch(url, {
|
|
55
|
+
method: "POST",
|
|
56
|
+
headers: {
|
|
57
|
+
"Content-Type": "application/json",
|
|
58
|
+
Authorization: `Bearer ${token}`,
|
|
59
|
+
},
|
|
60
|
+
body: JSON.stringify(firestoreData),
|
|
61
|
+
});
|
|
62
|
+
if (!response.ok) {
|
|
63
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
64
|
+
}
|
|
65
|
+
const result = (await response.json());
|
|
66
|
+
return (0, converter_1.convertFromFirestoreDocument)(result);
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* ドキュメントを取得
|
|
70
|
+
* @param collectionName コレクション名
|
|
71
|
+
* @param documentId ドキュメントID
|
|
72
|
+
* @returns 取得したドキュメント(存在しない場合はnull)
|
|
73
|
+
*/
|
|
74
|
+
async get(collectionName, documentId) {
|
|
75
|
+
const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName)}/${documentId}`;
|
|
76
|
+
const token = await this.getToken();
|
|
77
|
+
const response = await fetch(url, {
|
|
78
|
+
method: "GET",
|
|
79
|
+
headers: {
|
|
80
|
+
Authorization: `Bearer ${token}`,
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (response.status === 404) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
if (!response.ok) {
|
|
87
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
88
|
+
}
|
|
89
|
+
const result = (await response.json());
|
|
90
|
+
return (0, converter_1.convertFromFirestoreDocument)(result);
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* ドキュメントを更新
|
|
94
|
+
* @param collectionName コレクション名
|
|
95
|
+
* @param documentId ドキュメントID
|
|
96
|
+
* @param data 更新するデータ
|
|
97
|
+
* @returns 更新されたドキュメント
|
|
98
|
+
*/
|
|
99
|
+
async update(collectionName, documentId, data) {
|
|
100
|
+
const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName)}/${documentId}`;
|
|
101
|
+
const firestoreData = (0, converter_1.convertToFirestoreDocument)(data);
|
|
102
|
+
const token = await this.getToken();
|
|
103
|
+
const response = await fetch(url, {
|
|
104
|
+
method: "PATCH",
|
|
105
|
+
headers: {
|
|
106
|
+
"Content-Type": "application/json",
|
|
107
|
+
Authorization: `Bearer ${token}`,
|
|
108
|
+
},
|
|
109
|
+
body: JSON.stringify(firestoreData),
|
|
110
|
+
});
|
|
111
|
+
if (!response.ok) {
|
|
112
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
113
|
+
}
|
|
114
|
+
const result = (await response.json());
|
|
115
|
+
return (0, converter_1.convertFromFirestoreDocument)(result);
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* ドキュメントを削除
|
|
119
|
+
* @param collectionName コレクション名
|
|
120
|
+
* @param documentId ドキュメントID
|
|
121
|
+
* @returns 削除成功時はtrue
|
|
122
|
+
*/
|
|
123
|
+
async delete(collectionName, documentId) {
|
|
124
|
+
const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName)}/${documentId}`;
|
|
125
|
+
const token = await this.getToken();
|
|
126
|
+
const response = await fetch(url, {
|
|
127
|
+
method: "DELETE",
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${token}`,
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
if (!response.ok) {
|
|
133
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
134
|
+
}
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* コレクションのドキュメントを検索
|
|
139
|
+
* @param collectionName コレクション名
|
|
140
|
+
* @param options クエリオプション
|
|
141
|
+
* @returns 検索結果のドキュメント配列
|
|
142
|
+
*/
|
|
143
|
+
async query(collectionName, options = {}) {
|
|
144
|
+
const url = `${(0, path_1.getFirestoreBasePath)(this.config.projectId, collectionName)}:runQuery`;
|
|
145
|
+
// クエリ構築
|
|
146
|
+
const structuredQuery = {
|
|
147
|
+
from: [{ collectionId: collectionName }],
|
|
148
|
+
};
|
|
149
|
+
// フィルター条件
|
|
150
|
+
if (options.where && options.where.length > 0) {
|
|
151
|
+
structuredQuery.where = {
|
|
152
|
+
compositeFilter: {
|
|
153
|
+
op: "AND",
|
|
154
|
+
filters: options.where.map(condition => ({
|
|
155
|
+
fieldFilter: {
|
|
156
|
+
field: { fieldPath: condition.field },
|
|
157
|
+
op: condition.op,
|
|
158
|
+
value: (0, converter_1.convertToFirestoreValue)(condition.value),
|
|
159
|
+
},
|
|
160
|
+
})),
|
|
161
|
+
},
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
// 並べ替え
|
|
165
|
+
if (options.orderBy) {
|
|
166
|
+
structuredQuery.orderBy = [
|
|
167
|
+
{
|
|
168
|
+
field: { fieldPath: options.orderBy },
|
|
169
|
+
direction: "ASCENDING",
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
}
|
|
173
|
+
// 制限
|
|
174
|
+
if (options.limit) {
|
|
175
|
+
structuredQuery.limit = options.limit;
|
|
176
|
+
}
|
|
177
|
+
// オフセット
|
|
178
|
+
if (options.offset) {
|
|
179
|
+
structuredQuery.offset = options.offset;
|
|
180
|
+
}
|
|
181
|
+
const token = await this.getToken();
|
|
182
|
+
const response = await fetch(url, {
|
|
183
|
+
method: "POST",
|
|
184
|
+
headers: {
|
|
185
|
+
"Content-Type": "application/json",
|
|
186
|
+
Authorization: `Bearer ${token}`,
|
|
187
|
+
},
|
|
188
|
+
body: JSON.stringify({
|
|
189
|
+
structuredQuery,
|
|
190
|
+
}),
|
|
191
|
+
});
|
|
192
|
+
if (!response.ok) {
|
|
193
|
+
throw new Error(`Firestore API error: ${response.statusText}`);
|
|
194
|
+
}
|
|
195
|
+
const results = (await response.json());
|
|
196
|
+
return results
|
|
197
|
+
.filter(item => item.document)
|
|
198
|
+
.map(item => (0, converter_1.convertFromFirestoreDocument)(item.document));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
exports.FirestoreClient = FirestoreClient;
|
|
202
|
+
/**
|
|
203
|
+
* 新しいFirestoreクライアントインスタンスを作成
|
|
204
|
+
* @param config Firestore設定オブジェクト
|
|
205
|
+
* @returns FirestoreClientインスタンス
|
|
206
|
+
*/
|
|
207
|
+
function createFirestoreClient(config) {
|
|
208
|
+
return new FirestoreClient(config);
|
|
209
|
+
}
|