@cocreate/postgresql 1.0.0 → 1.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +7 -0
  2. package/package.json +13 -10
  3. package/src/index.js +70 -42
package/CHANGELOG.md CHANGED
@@ -1,3 +1,10 @@
1
+ # [1.1.0](https://github.com/CoCreate-app/CoCreate-postgresql/compare/v1.0.0...v1.1.0) (2026-07-18)
2
+
3
+
4
+ ### Features
5
+
6
+ * update package.json description and keywords; migrate to ES modules in index.js ([b91abfa](https://github.com/CoCreate-app/CoCreate-postgresql/commit/b91abfaaf64923dda537fa541e43c1fa8ea6e377))
7
+
1
8
  # 1.0.0 (2026-07-18)
2
9
 
3
10
 
package/package.json CHANGED
@@ -1,16 +1,19 @@
1
1
  {
2
2
  "name": "@cocreate/postgresql",
3
- "version": "1.0.0",
4
- "description": "A simple postgresql component in vanilla javascript. Easily configured using HTML5 data-attributes and/or JavaScript API.",
3
+ "version": "1.1.0",
4
+ "description": "Lightweight PostgreSQL CRUD wrapper and multi-tenant state routing client for the CoCreate ecosystem.",
5
5
  "keywords": [
6
6
  "postgresql",
7
- "low-code",
8
- "realtime",
9
- "realtime-framework",
10
- "collaboration",
11
- "shared-editing",
12
- "html5-framework",
13
- "javascript-framework"
7
+ "postgres",
8
+ "sql",
9
+ "jsonb",
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
- "postgresql": "^7.5.0"
44
+ "pg": "^8.22.0"
42
45
  }
43
46
  }
package/src/index.js CHANGED
@@ -1,11 +1,29 @@
1
- const { Pool } = require("pg");
2
- const {
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 pg from "pg";
19
+ const { Pool } = pg;
20
+ import {
3
21
  dotNotationToObject,
4
22
  queryData,
5
23
  searchData,
6
24
  sortData,
7
25
  isValidDate
8
- } = require("@cocreate/utils");
26
+ } from "@cocreate/utils";
9
27
 
10
28
  const organizations = {};
11
29
 
@@ -14,8 +32,9 @@ const organizations = {};
14
32
  */
15
33
  async function dbClient(data) {
16
34
  if (data.storageUrl) {
17
- if (!organizations[data.organization_id])
35
+ if (!organizations[data.organization_id]) {
18
36
  organizations[data.organization_id] = {};
37
+ }
19
38
  try {
20
39
  if (!organizations[data.organization_id][data.storageUrl]) {
21
40
  // Establish PostgreSQL Pool with optimized connection properties
@@ -29,7 +48,7 @@ async function dbClient(data) {
29
48
  return organizations[data.organization_id][data.storageUrl];
30
49
  } catch (error) {
31
50
  console.error(
32
- `${data.organization_id}: storageName ${data.storageName} failed to connect to PostgreSQL`
51
+ `${data.organization_id}: storageName ${data.storageName || "postgresql"} failed to connect to PostgreSQL`
33
52
  );
34
53
  errorHandler(data, error);
35
54
  return { status: false };
@@ -40,10 +59,41 @@ async function dbClient(data) {
40
59
  return;
41
60
  }
42
61
 
62
+ /**
63
+ * Global Event Listener: orgDeleted
64
+ * Gracefully terminates active PostgreSQL connection pools.
65
+ */
66
+ process.on("orgDeleted", (organization_id) => {
67
+ const orgPools = organizations[organization_id];
68
+ if (orgPools) {
69
+ console.log(`[PostgreSQL Utilities] Cleanup Scheduled: Initiating 5-second grace period for Org: ${organization_id}`);
70
+ setTimeout(async () => {
71
+ const currentPools = organizations[organization_id];
72
+ if (!currentPools) return;
73
+
74
+ console.log(`[PostgreSQL Utilities] Cleanup Executing: Closing connection pools for Org: ${organization_id}`);
75
+ const poolList = Object.values(currentPools);
76
+
77
+ for (const pool of poolList) {
78
+ try {
79
+ if (pool && typeof pool.end === "function") {
80
+ await pool.end();
81
+ }
82
+ } catch (err) {
83
+ console.error(`[PostgreSQL Utilities] Error closing connection pool for Org ${organization_id}:`, err);
84
+ }
85
+ }
86
+
87
+ delete organizations[organization_id];
88
+ console.log(`[PostgreSQL Utilities] Memory Cleared: Registry freed for Org: ${organization_id}`);
89
+ }, 5000);
90
+ }
91
+ });
92
+
43
93
  /**
44
94
  * Universal router matching the exact entry-point signature of the MongoDB wrapper.
45
95
  */
46
- function send(data) {
96
+ export function send(data) {
47
97
  let [type, method] = data.method.split(".");
48
98
  if (type === "database") return database(method, data);
49
99
  if (type === "array") return array(method, data);
@@ -61,7 +111,7 @@ function database(method, data) {
61
111
 
62
112
  try {
63
113
  const pool = await dbClient(data);
64
- if (!pool || pool.status === false) return data;
114
+ if (!pool || pool.status === false) return resolve(data);
65
115
 
66
116
  if (method === "read") {
67
117
  const sql = "SELECT datname FROM pg_database WHERE datistemplate = false;";
@@ -87,12 +137,12 @@ function database(method, data) {
87
137
  if (isFilter)
88
138
  databaseArray.push({
89
139
  database: dbObj,
90
- storage: data.storageName
140
+ storage: data.storageName || "postgresql"
91
141
  });
92
142
  } else {
93
143
  databaseArray.push({
94
144
  database: dbObj,
95
- storage: data.storageName
145
+ storage: data.storageName || "postgresql"
96
146
  });
97
147
  }
98
148
  }
@@ -122,9 +172,6 @@ function database(method, data) {
122
172
  console.log(method, "error", error);
123
173
  resolve(data);
124
174
  }
125
- },
126
- (error) => {
127
- errorHandler(data, error);
128
175
  }
129
176
  );
130
177
  }
@@ -140,7 +187,7 @@ function array(method, data) {
140
187
 
141
188
  try {
142
189
  const pool = await dbClient(data);
143
- if (!pool || pool.status === false) return data;
190
+ if (!pool || pool.status === false) return resolve(data);
144
191
 
145
192
  if (data.request) data.array = data.request;
146
193
 
@@ -178,13 +225,13 @@ function array(method, data) {
178
225
  arrayArray.push({
179
226
  name: row.table_name,
180
227
  database,
181
- storage: data.storageName
228
+ storage: data.storageName || "postgresql"
182
229
  });
183
230
  } else {
184
231
  arrayArray.push({
185
232
  name: row.table_name,
186
233
  database,
187
- storage: data.storageName
234
+ storage: data.storageName || "postgresql"
188
235
  });
189
236
  }
190
237
  }
@@ -222,7 +269,7 @@ function array(method, data) {
222
269
  arrayArray.push({
223
270
  name: array,
224
271
  database,
225
- storage: data.storageName
272
+ storage: data.storageName || "postgresql"
226
273
  });
227
274
 
228
275
  arraysLength -= 1;
@@ -247,7 +294,7 @@ function array(method, data) {
247
294
  name: newName,
248
295
  oldName: oldName,
249
296
  database,
250
- storage: data.storageName
297
+ storage: data.storageName || "postgresql"
251
298
  });
252
299
 
253
300
  arraysLength -= 1;
@@ -271,7 +318,7 @@ function array(method, data) {
271
318
  arrayArray.push({
272
319
  name: array,
273
320
  database,
274
- storage: data.storageName
321
+ storage: data.storageName || "postgresql"
275
322
  });
276
323
 
277
324
  arraysLength -= 1;
@@ -290,9 +337,6 @@ function array(method, data) {
290
337
  console.log(method, "error", error);
291
338
  resolve(data);
292
339
  }
293
- },
294
- (error) => {
295
- errorHandler(data, error);
296
340
  }
297
341
  );
298
342
  }
@@ -305,7 +349,7 @@ function object(method, data) {
305
349
  async (resolve, reject) => {
306
350
  try {
307
351
  const pool = await dbClient(data);
308
- if (!pool || pool.status === false) return data;
352
+ if (!pool || pool.status === false) return resolve(data);
309
353
 
310
354
  let type = "object";
311
355
  let documents = [];
@@ -322,7 +366,7 @@ function object(method, data) {
322
366
 
323
367
  for (let array of arrays) {
324
368
  const reference = {
325
- $storage: data.storageName,
369
+ $storage: data.storageName || "postgresql",
326
370
  $database: database,
327
371
  $array: array
328
372
  };
@@ -347,7 +391,7 @@ function object(method, data) {
347
391
  if (!Array.isArray($database)) $database = [data[type][i].$database];
348
392
  if (!Array.isArray($array)) $array = [data[type][i].$array];
349
393
 
350
- if (!$storage.includes(data.storageName)) $storage.push(data.storageName);
394
+ if (!$storage.includes(data.storageName || "postgresql")) $storage.push(data.storageName || "postgresql");
351
395
  if (!$database.includes(database)) $database.push(database);
352
396
  if (!$array.includes(array)) $array.push(array);
353
397
 
@@ -439,7 +483,6 @@ function object(method, data) {
439
483
  if (isFilter) {
440
484
  for (let row of result.rows) {
441
485
  const parsedRow = parseSqlRow(row);
442
- // Perform dynamic text searching if requested
443
486
  if (data.$filter && data.$filter.search) {
444
487
  let isMatch = searchData(parsedRow, data.$filter.search);
445
488
  if (!isMatch) continue;
@@ -452,18 +495,15 @@ function object(method, data) {
452
495
  } else if (result.rows.length > 0) {
453
496
  let dbRecord = parseSqlRow(result.rows[0]);
454
497
 
455
- // Offline synchronization logic
456
498
  if ($storage.length && data[type][i].modified && data[type][i].modified.on) {
457
499
  let clientTime = new Date(data[type][i].modified.on);
458
500
  let dbTime = new Date(dbRecord.modified ? dbRecord.modified.on : 0);
459
501
 
460
502
  if (clientTime > dbTime) {
461
- // Client modified newer: perform a full synchronization merge and commit write
462
503
  const updatedRecord = { ...dbRecord, ...data[type][i] };
463
504
  const savedRecord = await executeUpdateTransaction(pool, array, data[type][i]._id, updatedRecord, data);
464
505
  data[type][i] = { ...savedRecord, $storage, $database, $array };
465
506
  } else {
466
- // DB has newer or identical stamp: resolve with database state
467
507
  data[type][i] = { ...data[type][i], ...dbRecord, $storage, $database, $array };
468
508
  }
469
509
  } else {
@@ -489,7 +529,6 @@ function object(method, data) {
489
529
  data[type][i] = { ...updatedDoc, $storage, $database, $array };
490
530
  }
491
531
  } else if (isFilter) {
492
- // Read target row IDs first for consistent processing
493
532
  const selectSql = `SELECT _id FROM "${array}" ${sqlWhere}`;
494
533
  process.emit("usage", {
495
534
  type: "egress",
@@ -550,9 +589,6 @@ function object(method, data) {
550
589
  console.log(method, "error", error);
551
590
  resolve(data);
552
591
  }
553
- },
554
- (error) => {
555
- errorHandler(data, error);
556
592
  }
557
593
  );
558
594
  }
@@ -565,7 +601,6 @@ async function executeUpdateTransaction(pool, arrayName, docId, rawUpdateInput,
565
601
  try {
566
602
  await client.query("BEGIN;");
567
603
 
568
- // Row lock the targeted document preventing race conditions
569
604
  const selectSql = `SELECT * FROM "${arrayName}" WHERE _id = $1 AND organization_id = $2 FOR UPDATE;`;
570
605
  const selectParams = [docId, requestData.organization_id];
571
606
 
@@ -581,16 +616,13 @@ async function executeUpdateTransaction(pool, arrayName, docId, rawUpdateInput,
581
616
  }
582
617
  }
583
618
 
584
- // Compute updated structural fields and payload blocks
585
619
  const modified = {
586
620
  on: new Date(requestData.timeStamp),
587
621
  by: requestData.user_id || requestData.clientId
588
622
  };
589
623
 
590
- // Run deep JavaScript nested update mutator reproducing MongoDB operator behavior
591
624
  const updatedPayload = applyMongoUpdate(currentRecord, rawUpdateInput);
592
625
 
593
- // Clean out system structures before committing payload to SQL database JSONB field
594
626
  const savePayload = { ...updatedPayload };
595
627
  delete savePayload._id;
596
628
  delete savePayload.organization_id;
@@ -637,15 +669,13 @@ async function executeUpdateTransaction(pool, arrayName, docId, rawUpdateInput,
637
669
  function applyMongoUpdate(record, updatePayload) {
638
670
  let target = { ...record };
639
671
 
640
- // Process set operators
641
672
  Object.keys(updatePayload).forEach((rawKey) => {
642
- if (rawKey.startsWith("$")) return; // Skip operators for raw fields processing
673
+ if (rawKey.startsWith("$")) return;
643
674
 
644
675
  let cleanKey = rawKey.replace(/\[(\d+)\]/g, ".$1");
645
676
  setNestedValue(target, cleanKey, updatePayload[rawKey]);
646
677
  });
647
678
 
648
- // Process Mongo Operators: $set, $unset, $push, $pull, $addToSet, $inc
649
679
  if (updatePayload.$set) {
650
680
  Object.keys(updatePayload.$set).forEach((key) => {
651
681
  let cleanKey = key.replace(/\[(\d+)\]/g, ".$1");
@@ -736,7 +766,6 @@ function compileFilterToSQL(filter, organizationId) {
736
766
  const parseConditionGroup = (queryObj, isOrJoin = false) => {
737
767
  let localClauses = [];
738
768
 
739
- // Special Handling for nested boolean operators ($or, $and)
740
769
  if (queryObj.$or && Array.isArray(queryObj.$or)) {
741
770
  let orClauses = queryObj.$or.map(subQuery => parseConditionGroup(subQuery)).filter(c => c.length);
742
771
  if (orClauses.length) {
@@ -750,7 +779,6 @@ function compileFilterToSQL(filter, organizationId) {
750
779
  }
751
780
  }
752
781
 
753
- // Process fields recursively supporting dot path targets
754
782
  for (let key in queryObj) {
755
783
  if (["$or", "$and"].includes(key)) continue;
756
784
 
@@ -890,4 +918,4 @@ function errorHandler(data, error, database, array) {
890
918
  }
891
919
  }
892
920
 
893
- module.exports = { send };
921
+ export default { send };