@cocreate/firestore 1.0.0 → 1.2.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/CHANGELOG.md +14 -0
- package/package.json +13 -10
- package/src/index.js +66 -31
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
# [1.2.0](https://github.com/CoCreate-app/CoCreate-firestore/compare/v1.1.0...v1.2.0) (2026-07-18)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* Update package.json description and keywords; refactor index.js to use ES modules ([1764e13](https://github.com/CoCreate-app/CoCreate-firestore/commit/1764e13f050671a8d2b55609bf118bcf9c510e17))
|
|
7
|
+
|
|
8
|
+
# [1.1.0](https://github.com/CoCreate-app/CoCreate-firestore/compare/v1.0.0...v1.1.0) (2026-07-18)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* Enhance Firestore client connection handling and normalize storage URL usage ([a97f76b](https://github.com/CoCreate-app/CoCreate-firestore/commit/a97f76b0dde45697e24875af2436e3b53c3b5b8f))
|
|
14
|
+
|
|
1
15
|
# 1.0.0 (2026-07-18)
|
|
2
16
|
|
|
3
17
|
|
package/package.json
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cocreate/firestore",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"description": "Lightweight Google Cloud Firestore CRUD wrapper and multi-tenant state routing client for the CoCreate ecosystem.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"firestore",
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
7
|
+
"firebase",
|
|
8
|
+
"google-cloud-firestore",
|
|
9
|
+
"nosql",
|
|
10
|
+
"crud-api",
|
|
11
|
+
"abstract-crud",
|
|
12
|
+
"database-abstraction",
|
|
13
|
+
"multi-tenant",
|
|
14
|
+
"multi-tenant-routing",
|
|
15
|
+
"realtime-sync",
|
|
16
|
+
"state-synchronization"
|
|
14
17
|
],
|
|
15
18
|
"publishConfig": {
|
|
16
19
|
"access": "public"
|
|
@@ -38,6 +41,6 @@
|
|
|
38
41
|
"main": "./src/index.js",
|
|
39
42
|
"dependencies": {
|
|
40
43
|
"@cocreate/utils": "^1.44.0",
|
|
41
|
-
"firestore": "^7.5.0"
|
|
44
|
+
"@google-cloud/firestore": "^7.5.0"
|
|
42
45
|
}
|
|
43
46
|
}
|
package/src/index.js
CHANGED
|
@@ -1,37 +1,70 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (C) 2026 CoCreate and Contributors.
|
|
3
|
+
*
|
|
4
|
+
* This program is free software: you can redistribute it and/or modify
|
|
5
|
+
* it under the terms of the GNU Affero General Public License as published
|
|
6
|
+
* by the Free Software Foundation, either version 3 of the License, or
|
|
7
|
+
* (at your option) any later version.
|
|
8
|
+
*
|
|
9
|
+
* This program is distributed in the hope that it will be useful,
|
|
10
|
+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11
|
+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
12
|
+
* GNU Affero General Public License for more details.
|
|
13
|
+
*
|
|
14
|
+
* You should have received a copy of the GNU Affero General Public License
|
|
15
|
+
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
16
|
+
********************************************************************************/
|
|
17
|
+
|
|
18
|
+
import { Firestore } from "@google-cloud/firestore";
|
|
19
|
+
import {
|
|
3
20
|
dotNotationToObject,
|
|
4
21
|
queryData,
|
|
5
22
|
searchData,
|
|
6
23
|
sortData,
|
|
7
24
|
isValidDate
|
|
8
|
-
}
|
|
25
|
+
} from "@cocreate/utils";
|
|
9
26
|
|
|
10
27
|
const organizations = {};
|
|
11
28
|
|
|
12
29
|
/**
|
|
13
30
|
* Dynamically resolves or caches a long-lived Firestore database client connection.
|
|
14
31
|
* Caches the connection promise to prevent duplicate authentication handshakes.
|
|
15
|
-
* Normalizes custom credential protocols directly out of standard CoCreate storageUrls.
|
|
32
|
+
* Normalizes custom credential protocols directly out of standard CoCreate storageUrls or urls.
|
|
16
33
|
*/
|
|
17
34
|
async function dbClient(data) {
|
|
18
|
-
|
|
35
|
+
const storageUrl = data.storageUrl || data.url;
|
|
36
|
+
|
|
37
|
+
if (storageUrl) {
|
|
19
38
|
if (!organizations[data.organization_id]) {
|
|
20
39
|
organizations[data.organization_id] = {};
|
|
21
40
|
}
|
|
22
41
|
try {
|
|
23
|
-
if (!organizations[data.organization_id][
|
|
42
|
+
if (!organizations[data.organization_id][storageUrl]) {
|
|
24
43
|
const connectionPromise = (async () => {
|
|
25
44
|
let config = {};
|
|
26
45
|
|
|
27
|
-
// Option A: The Credentials
|
|
28
|
-
if (
|
|
29
|
-
const
|
|
46
|
+
// Option A: The Structured Credentials Object (Highly recommended)
|
|
47
|
+
if (typeof storageUrl === "object" && storageUrl !== null) {
|
|
48
|
+
const projectId = storageUrl.projectId || storageUrl.project_id;
|
|
49
|
+
const clientEmail = storageUrl.clientEmail || storageUrl.client_email;
|
|
50
|
+
const privateKey = storageUrl.privateKey || storageUrl.private_key;
|
|
51
|
+
|
|
52
|
+
config = {
|
|
53
|
+
projectId,
|
|
54
|
+
credentials: {
|
|
55
|
+
client_email: clientEmail,
|
|
56
|
+
private_key: privateKey ? privateKey.replace(/\\n/g, "\n") : undefined
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// Option B: The Credentials Path URL (e.g. firestore:///path/to/credentials.json)
|
|
61
|
+
else if (typeof storageUrl === "string" && storageUrl.startsWith("firestore:///")) {
|
|
62
|
+
const filePath = storageUrl.replace("firestore://", "");
|
|
30
63
|
config = { keyFilename: filePath };
|
|
31
64
|
}
|
|
32
|
-
// Option
|
|
33
|
-
else if (
|
|
34
|
-
const urlParsed = new URL(
|
|
65
|
+
// Option C: Query Params Encoded URI (e.g. firestore://project-id?client_email=...&private_key=...)
|
|
66
|
+
else if (typeof storageUrl === "string" && storageUrl.startsWith("firestore://")) {
|
|
67
|
+
const urlParsed = new URL(storageUrl);
|
|
35
68
|
const projectId = urlParsed.host || urlParsed.hostname;
|
|
36
69
|
const clientEmail = urlParsed.searchParams.get("client_email");
|
|
37
70
|
const privateKey = urlParsed.searchParams.get("private_key");
|
|
@@ -57,21 +90,21 @@ async function dbClient(data) {
|
|
|
57
90
|
return client;
|
|
58
91
|
})();
|
|
59
92
|
|
|
60
|
-
organizations[data.organization_id][
|
|
93
|
+
organizations[data.organization_id][storageUrl] = connectionPromise;
|
|
61
94
|
}
|
|
62
95
|
|
|
63
96
|
// Await connection resolution safely
|
|
64
|
-
return await organizations[data.organization_id][
|
|
97
|
+
return await organizations[data.organization_id][storageUrl];
|
|
65
98
|
} catch (error) {
|
|
66
99
|
console.error(
|
|
67
|
-
`${data.organization_id}:
|
|
100
|
+
`${data.organization_id}: Firestore failed to connect`
|
|
68
101
|
);
|
|
69
102
|
errorHandler(data, error);
|
|
70
103
|
return { status: false };
|
|
71
104
|
}
|
|
72
105
|
}
|
|
73
106
|
|
|
74
|
-
errorHandler(data, "missing
|
|
107
|
+
errorHandler(data, "missing Firestore Connection URL/Credentials");
|
|
75
108
|
return;
|
|
76
109
|
}
|
|
77
110
|
|
|
@@ -110,7 +143,7 @@ process.on("orgDeleted", (organization_id) => {
|
|
|
110
143
|
/**
|
|
111
144
|
* Universal router matching the standard CoCreate pipeline signature.
|
|
112
145
|
*/
|
|
113
|
-
function send(data) {
|
|
146
|
+
export function send(data) {
|
|
114
147
|
let [type, method] = data.method.split(".");
|
|
115
148
|
if (type === "database") return database(method, data);
|
|
116
149
|
if (type === "array") return array(method, data);
|
|
@@ -121,6 +154,7 @@ function send(data) {
|
|
|
121
154
|
* "database" operations mapped to Firestore Databases.
|
|
122
155
|
*/
|
|
123
156
|
function database(method, data) {
|
|
157
|
+
const storageName = data.storageName || data.provider || "firestore";
|
|
124
158
|
return new Promise(
|
|
125
159
|
async (resolve, reject) => {
|
|
126
160
|
let type = "database";
|
|
@@ -128,7 +162,7 @@ function database(method, data) {
|
|
|
128
162
|
|
|
129
163
|
try {
|
|
130
164
|
const db = await dbClient(data);
|
|
131
|
-
if (!db || db.status === false) return data;
|
|
165
|
+
if (!db || db.status === false) return resolve(data);
|
|
132
166
|
|
|
133
167
|
if (method === "read") {
|
|
134
168
|
// Firestore is serverless and generally operates on a single default schema database per context.
|
|
@@ -147,13 +181,13 @@ function database(method, data) {
|
|
|
147
181
|
if (isFilter) {
|
|
148
182
|
databaseArray.push({
|
|
149
183
|
database: dbObj,
|
|
150
|
-
storage:
|
|
184
|
+
storage: storageName
|
|
151
185
|
});
|
|
152
186
|
}
|
|
153
187
|
} else {
|
|
154
188
|
databaseArray.push({
|
|
155
189
|
database: dbObj,
|
|
156
|
-
storage:
|
|
190
|
+
storage: storageName
|
|
157
191
|
});
|
|
158
192
|
}
|
|
159
193
|
resolve(createData(data, databaseArray, type));
|
|
@@ -187,6 +221,7 @@ function database(method, data) {
|
|
|
187
221
|
* "array" operations mapped to Firestore Collections.
|
|
188
222
|
*/
|
|
189
223
|
function array(method, data) {
|
|
224
|
+
const storageName = data.storageName || data.provider || "firestore";
|
|
190
225
|
return new Promise(
|
|
191
226
|
async (resolve, reject) => {
|
|
192
227
|
let type = "array";
|
|
@@ -194,7 +229,7 @@ function array(method, data) {
|
|
|
194
229
|
|
|
195
230
|
try {
|
|
196
231
|
const db = await dbClient(data);
|
|
197
|
-
if (!db || db.status === false) return data;
|
|
232
|
+
if (!db || db.status === false) return resolve(data);
|
|
198
233
|
|
|
199
234
|
if (data.request) data.array = data.request;
|
|
200
235
|
|
|
@@ -220,14 +255,14 @@ function array(method, data) {
|
|
|
220
255
|
arrayArray.push({
|
|
221
256
|
name: col.id,
|
|
222
257
|
database,
|
|
223
|
-
storage:
|
|
258
|
+
storage: storageName
|
|
224
259
|
});
|
|
225
260
|
}
|
|
226
261
|
} else {
|
|
227
262
|
arrayArray.push({
|
|
228
263
|
name: col.id,
|
|
229
264
|
database,
|
|
230
|
-
storage:
|
|
265
|
+
storage: storageName
|
|
231
266
|
});
|
|
232
267
|
}
|
|
233
268
|
}
|
|
@@ -260,7 +295,7 @@ function array(method, data) {
|
|
|
260
295
|
arrayArray.push({
|
|
261
296
|
name: array,
|
|
262
297
|
database,
|
|
263
|
-
storage:
|
|
298
|
+
storage: storageName
|
|
264
299
|
});
|
|
265
300
|
|
|
266
301
|
arraysLength -= 1;
|
|
@@ -295,7 +330,7 @@ function array(method, data) {
|
|
|
295
330
|
name: newName,
|
|
296
331
|
oldName: oldName,
|
|
297
332
|
database,
|
|
298
|
-
storage:
|
|
333
|
+
storage: storageName
|
|
299
334
|
});
|
|
300
335
|
|
|
301
336
|
arraysLength -= 1;
|
|
@@ -319,7 +354,7 @@ function array(method, data) {
|
|
|
319
354
|
arrayArray.push({
|
|
320
355
|
name: array,
|
|
321
356
|
database,
|
|
322
|
-
storage:
|
|
357
|
+
storage: storageName
|
|
323
358
|
});
|
|
324
359
|
|
|
325
360
|
arraysLength -= 1;
|
|
@@ -349,11 +384,12 @@ function array(method, data) {
|
|
|
349
384
|
* "object" operations mapped to Firestore Documents.
|
|
350
385
|
*/
|
|
351
386
|
function object(method, data) {
|
|
387
|
+
const storageName = data.storageName || data.provider || "firestore";
|
|
352
388
|
return new Promise(
|
|
353
389
|
async (resolve, reject) => {
|
|
354
390
|
try {
|
|
355
391
|
const db = await dbClient(data);
|
|
356
|
-
if (!db || db.status === false) return data;
|
|
392
|
+
if (!db || db.status === false) return resolve(data);
|
|
357
393
|
|
|
358
394
|
let type = "object";
|
|
359
395
|
let documents = [];
|
|
@@ -371,7 +407,7 @@ function object(method, data) {
|
|
|
371
407
|
|
|
372
408
|
for (let array of arrays) {
|
|
373
409
|
const reference = {
|
|
374
|
-
$storage:
|
|
410
|
+
$storage: storageName,
|
|
375
411
|
$database: database,
|
|
376
412
|
$array: array
|
|
377
413
|
};
|
|
@@ -396,7 +432,7 @@ function object(method, data) {
|
|
|
396
432
|
if (!Array.isArray($database)) $database = [data[type][i].$database];
|
|
397
433
|
if (!Array.isArray($array)) $array = [data[type][i].$array];
|
|
398
434
|
|
|
399
|
-
if (!$storage.includes(
|
|
435
|
+
if (!$storage.includes(storageName)) $storage.push(storageName);
|
|
400
436
|
if (!$database.includes(database)) $database.push(database);
|
|
401
437
|
if (!$array.includes(array)) $array.push(array);
|
|
402
438
|
|
|
@@ -456,7 +492,6 @@ function object(method, data) {
|
|
|
456
492
|
queryRef = queryRef.limit(data.$filter.limit);
|
|
457
493
|
}
|
|
458
494
|
if (data.$filter && typeof data.$filter.index === "number") {
|
|
459
|
-
// Standard offset queries are supported but startAt is normally preferred in production
|
|
460
495
|
queryRef = queryRef.offset(data.$filter.index);
|
|
461
496
|
}
|
|
462
497
|
|
|
@@ -844,4 +879,4 @@ function errorHandler(data, error, database, array) {
|
|
|
844
879
|
}
|
|
845
880
|
}
|
|
846
881
|
|
|
847
|
-
|
|
882
|
+
export default { send };
|