@arcote.tech/arc-adapter-db-postgres 0.3.0 → 0.3.2
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/package.json +2 -3
- package/src/postgres-adapter.ts +146 -21
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@arcote.tech/arc-adapter-db-postgres",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -20,10 +20,9 @@
|
|
|
20
20
|
"postgres": "^3.4.4"
|
|
21
21
|
},
|
|
22
22
|
"peerDependencies": {
|
|
23
|
-
"@arcote.tech/arc": "
|
|
23
|
+
"@arcote.tech/arc": "^0.3.0"
|
|
24
24
|
},
|
|
25
25
|
"devDependencies": {
|
|
26
|
-
"@arcote.tech/arc": "workspace:*",
|
|
27
26
|
"@types/pg": "^8.11.0",
|
|
28
27
|
"typescript": "^5.0.0"
|
|
29
28
|
}
|
package/src/postgres-adapter.ts
CHANGED
|
@@ -67,8 +67,24 @@ class PostgreSQLReadTransaction implements ReadTransaction {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
return value;
|
|
70
|
+
case "date":
|
|
70
71
|
case "datetime":
|
|
71
72
|
case "timestamp":
|
|
73
|
+
// PostgreSQL returns Date objects directly via postgres.js driver
|
|
74
|
+
if (value instanceof Date) {
|
|
75
|
+
return value;
|
|
76
|
+
}
|
|
77
|
+
if (typeof value === "string") {
|
|
78
|
+
// If the string doesn't have timezone info, treat as UTC
|
|
79
|
+
if (
|
|
80
|
+
!value.endsWith("Z") &&
|
|
81
|
+
!value.includes("+") &&
|
|
82
|
+
!value.includes("-", 10)
|
|
83
|
+
) {
|
|
84
|
+
return new Date(value + "Z");
|
|
85
|
+
}
|
|
86
|
+
return new Date(value);
|
|
87
|
+
}
|
|
72
88
|
return new Date(value);
|
|
73
89
|
default:
|
|
74
90
|
return value;
|
|
@@ -99,6 +115,21 @@ class PostgreSQLReadTransaction implements ReadTransaction {
|
|
|
99
115
|
const params: any[] = [];
|
|
100
116
|
let paramIndex = 1;
|
|
101
117
|
|
|
118
|
+
// Debug logging
|
|
119
|
+
if (where && (Array.isArray(where) || typeof where !== "object")) {
|
|
120
|
+
console.error(
|
|
121
|
+
"[buildWhereClause] Invalid where clause type:",
|
|
122
|
+
typeof where,
|
|
123
|
+
Array.isArray(where) ? "(array)" : "",
|
|
124
|
+
);
|
|
125
|
+
console.error(
|
|
126
|
+
"[buildWhereClause] where value:",
|
|
127
|
+
JSON.stringify(where, null, 2),
|
|
128
|
+
);
|
|
129
|
+
console.error("[buildWhereClause] tableName:", tableName);
|
|
130
|
+
console.trace("[buildWhereClause] Stack trace:");
|
|
131
|
+
}
|
|
132
|
+
|
|
102
133
|
// Only add deleted condition if table has soft delete enabled
|
|
103
134
|
if (tableName && this.hasSoftDelete(tableName)) {
|
|
104
135
|
conditions.push('"deleted" = $1');
|
|
@@ -160,7 +191,7 @@ class PostgreSQLReadTransaction implements ReadTransaction {
|
|
|
160
191
|
});
|
|
161
192
|
|
|
162
193
|
return {
|
|
163
|
-
sql: conditions.join(" AND "),
|
|
194
|
+
sql: conditions.length > 0 ? conditions.join(" AND ") : "1=1",
|
|
164
195
|
params,
|
|
165
196
|
};
|
|
166
197
|
}
|
|
@@ -258,12 +289,30 @@ class PostgreSQLReadWriteTransaction
|
|
|
258
289
|
|
|
259
290
|
const hasVersioning = this.adapter.hasVersioning(store);
|
|
260
291
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
292
|
+
try {
|
|
293
|
+
if (hasVersioning) {
|
|
294
|
+
// For versioned tables, use a transaction with version increment
|
|
295
|
+
await this.setWithVersioning(store, item, table);
|
|
296
|
+
} else {
|
|
297
|
+
// For non-versioned tables, use simple insert
|
|
298
|
+
await this.setWithoutVersioning(store, item, table);
|
|
299
|
+
}
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error(`[PostgreSQL] Error in set() for store "${store}":`);
|
|
302
|
+
console.error(
|
|
303
|
+
`[PostgreSQL] Item data:`,
|
|
304
|
+
JSON.stringify(
|
|
305
|
+
item,
|
|
306
|
+
(key, value) => {
|
|
307
|
+
if (value instanceof Date) {
|
|
308
|
+
return `[Date: ${value.toString()} | ISO: ${isNaN(value.getTime()) ? "Invalid Date" : value.toISOString()}]`;
|
|
309
|
+
}
|
|
310
|
+
return value;
|
|
311
|
+
},
|
|
312
|
+
2,
|
|
313
|
+
),
|
|
314
|
+
);
|
|
315
|
+
throw error;
|
|
267
316
|
}
|
|
268
317
|
}
|
|
269
318
|
|
|
@@ -381,13 +430,31 @@ class PostgreSQLReadWriteTransaction
|
|
|
381
430
|
switch (column.type.toLowerCase()) {
|
|
382
431
|
case "timestamp":
|
|
383
432
|
case "datetime":
|
|
433
|
+
case "timestamptz":
|
|
384
434
|
// Handle various timestamp formats
|
|
385
435
|
if (value instanceof Date) {
|
|
436
|
+
if (isNaN(value.getTime())) {
|
|
437
|
+
console.error(
|
|
438
|
+
`[PostgreSQL] Invalid Date in column "${column.name}":`,
|
|
439
|
+
value,
|
|
440
|
+
);
|
|
441
|
+
console.error(`[PostgreSQL] Column type: ${column.type}`);
|
|
442
|
+
throw new Error(`Invalid Date value in column "${column.name}"`);
|
|
443
|
+
}
|
|
386
444
|
return value.toISOString();
|
|
387
445
|
}
|
|
388
446
|
if (typeof value === "number") {
|
|
389
447
|
// Unix timestamp (seconds or milliseconds)
|
|
390
448
|
const date = value > 1e10 ? new Date(value) : new Date(value * 1000);
|
|
449
|
+
if (isNaN(date.getTime())) {
|
|
450
|
+
console.error(
|
|
451
|
+
`[PostgreSQL] Invalid Date from number in column "${column.name}":`,
|
|
452
|
+
value,
|
|
453
|
+
);
|
|
454
|
+
throw new Error(
|
|
455
|
+
`Invalid Date value (from number) in column "${column.name}"`,
|
|
456
|
+
);
|
|
457
|
+
}
|
|
391
458
|
return date.toISOString();
|
|
392
459
|
}
|
|
393
460
|
if (typeof value === "string") {
|
|
@@ -396,13 +463,34 @@ class PostgreSQLReadWriteTransaction
|
|
|
396
463
|
if (!isNaN(date.getTime())) {
|
|
397
464
|
return date.toISOString();
|
|
398
465
|
}
|
|
466
|
+
console.error(
|
|
467
|
+
`[PostgreSQL] Invalid Date string in column "${column.name}":`,
|
|
468
|
+
value,
|
|
469
|
+
);
|
|
470
|
+
throw new Error(
|
|
471
|
+
`Invalid Date string in column "${column.name}": "${value}"`,
|
|
472
|
+
);
|
|
399
473
|
}
|
|
400
|
-
|
|
474
|
+
console.error(
|
|
475
|
+
`[PostgreSQL] Unexpected date value type in column "${column.name}":`,
|
|
476
|
+
typeof value,
|
|
477
|
+
value,
|
|
478
|
+
);
|
|
479
|
+
throw new Error(
|
|
480
|
+
`Unexpected date value type in column "${column.name}": ${typeof value}`,
|
|
481
|
+
);
|
|
401
482
|
case "json":
|
|
402
483
|
case "jsonb":
|
|
403
484
|
return JSON.stringify(value);
|
|
404
485
|
default:
|
|
405
486
|
if (value instanceof Date) {
|
|
487
|
+
if (isNaN(value.getTime())) {
|
|
488
|
+
console.error(
|
|
489
|
+
`[PostgreSQL] Invalid Date in column "${column.name}" (default case):`,
|
|
490
|
+
value,
|
|
491
|
+
);
|
|
492
|
+
throw new Error(`Invalid Date value in column "${column.name}"`);
|
|
493
|
+
}
|
|
406
494
|
return value.toISOString();
|
|
407
495
|
}
|
|
408
496
|
if (Array.isArray(value) || typeof value === "object") {
|
|
@@ -440,7 +528,7 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
440
528
|
case "boolean":
|
|
441
529
|
return "BOOLEAN";
|
|
442
530
|
case "date":
|
|
443
|
-
return "
|
|
531
|
+
return "TIMESTAMPTZ";
|
|
444
532
|
case "object":
|
|
445
533
|
case "array":
|
|
446
534
|
case "record":
|
|
@@ -452,7 +540,10 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
452
540
|
}
|
|
453
541
|
}
|
|
454
542
|
|
|
455
|
-
private buildConstraints(
|
|
543
|
+
private buildConstraints(
|
|
544
|
+
storeData?: DatabaseStoreData,
|
|
545
|
+
skipForeignKey = false,
|
|
546
|
+
): string[] {
|
|
456
547
|
const constraints: string[] = [];
|
|
457
548
|
|
|
458
549
|
if (storeData?.isPrimaryKey) {
|
|
@@ -461,13 +552,8 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
461
552
|
if (storeData?.isUnique) {
|
|
462
553
|
constraints.push("UNIQUE");
|
|
463
554
|
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
let fkConstraint = `REFERENCES ${table}(${column})`;
|
|
467
|
-
if (onDelete) fkConstraint += ` ON DELETE ${onDelete}`;
|
|
468
|
-
if (onUpdate) fkConstraint += ` ON UPDATE ${onUpdate}`;
|
|
469
|
-
constraints.push(fkConstraint);
|
|
470
|
-
}
|
|
555
|
+
// Foreign keys are now added separately after all tables are created
|
|
556
|
+
// to avoid dependency ordering issues
|
|
471
557
|
|
|
472
558
|
return constraints;
|
|
473
559
|
}
|
|
@@ -500,7 +586,7 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
500
586
|
private generateCreateTableSQL(
|
|
501
587
|
tableName: string,
|
|
502
588
|
columns: (DatabaseAgnosticColumnInfo & { name: string })[],
|
|
503
|
-
): string {
|
|
589
|
+
): { createTableSql: string; foreignKeySql: string[] } {
|
|
504
590
|
const columnDefinitions = columns.map((col) => this.generateColumnSQL(col));
|
|
505
591
|
const indexes = columns
|
|
506
592
|
.filter((col) => col.storeData?.hasIndex && !col.storeData?.isPrimaryKey)
|
|
@@ -509,13 +595,25 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
509
595
|
`CREATE INDEX IF NOT EXISTS idx_${tableName}_${col.name} ON "${tableName}"("${col.name}");`,
|
|
510
596
|
);
|
|
511
597
|
|
|
598
|
+
// Collect foreign key constraints to add after table creation
|
|
599
|
+
const foreignKeySql: string[] = [];
|
|
600
|
+
for (const col of columns) {
|
|
601
|
+
if (col.storeData?.foreignKey) {
|
|
602
|
+
const { table, column, onDelete, onUpdate } = col.storeData.foreignKey;
|
|
603
|
+
let fkSql = `ALTER TABLE "${tableName}" ADD CONSTRAINT fk_${tableName}_${col.name} FOREIGN KEY ("${col.name}") REFERENCES "${table}"("${column}")`;
|
|
604
|
+
if (onDelete) fkSql += ` ON DELETE ${onDelete}`;
|
|
605
|
+
if (onUpdate) fkSql += ` ON UPDATE ${onUpdate}`;
|
|
606
|
+
foreignKeySql.push(fkSql);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
|
|
512
610
|
let sql = `CREATE TABLE IF NOT EXISTS "${tableName}" (\n ${columnDefinitions.join(",\n ")}\n)`;
|
|
513
611
|
|
|
514
612
|
if (indexes.length > 0) {
|
|
515
613
|
sql += ";\n" + indexes.join("\n");
|
|
516
614
|
}
|
|
517
615
|
|
|
518
|
-
return sql;
|
|
616
|
+
return { createTableSql: sql, foreignKeySql };
|
|
519
617
|
}
|
|
520
618
|
|
|
521
619
|
constructor(
|
|
@@ -570,6 +668,8 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
570
668
|
});
|
|
571
669
|
}
|
|
572
670
|
|
|
671
|
+
private pendingForeignKeys: string[] = [];
|
|
672
|
+
|
|
573
673
|
public async initialize() {
|
|
574
674
|
// Create the version counter table first
|
|
575
675
|
await this.createVersionCounterTable();
|
|
@@ -579,6 +679,9 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
579
679
|
const processedSchemas = new Set<DatabaseStoreSchema>();
|
|
580
680
|
const processedTables = new Set<string>();
|
|
581
681
|
|
|
682
|
+
// Clear pending foreign keys
|
|
683
|
+
this.pendingForeignKeys = [];
|
|
684
|
+
|
|
582
685
|
for (const element of this.context.elements) {
|
|
583
686
|
if (
|
|
584
687
|
"databaseStoreSchema" in element &&
|
|
@@ -687,14 +790,34 @@ export class PostgreSQLAdapter implements DatabaseAdapter {
|
|
|
687
790
|
}
|
|
688
791
|
}
|
|
689
792
|
}
|
|
793
|
+
|
|
794
|
+
// Apply all foreign key constraints after all tables are created
|
|
795
|
+
for (const fkSql of this.pendingForeignKeys) {
|
|
796
|
+
try {
|
|
797
|
+
await this.db.exec(fkSql);
|
|
798
|
+
} catch (error: any) {
|
|
799
|
+
// Ignore if constraint already exists
|
|
800
|
+
if (!error.message?.includes("already exists")) {
|
|
801
|
+
console.warn(
|
|
802
|
+
`Failed to add foreign key constraint: ${error.message}`,
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
this.pendingForeignKeys = [];
|
|
690
808
|
}
|
|
691
809
|
|
|
692
810
|
private async createTableIfNotExistsNew(
|
|
693
811
|
tableName: string,
|
|
694
812
|
columns: (DatabaseAgnosticColumnInfo & { name: string })[],
|
|
695
813
|
) {
|
|
696
|
-
const
|
|
697
|
-
|
|
814
|
+
const { createTableSql, foreignKeySql } = this.generateCreateTableSQL(
|
|
815
|
+
tableName,
|
|
816
|
+
columns,
|
|
817
|
+
);
|
|
818
|
+
await this.db.exec(createTableSql);
|
|
819
|
+
// Collect foreign keys to be added after all tables are created
|
|
820
|
+
this.pendingForeignKeys.push(...foreignKeySql);
|
|
698
821
|
}
|
|
699
822
|
|
|
700
823
|
/**
|
|
@@ -834,6 +957,8 @@ class PostgresJsDatabase implements PostgreSQLDatabase {
|
|
|
834
957
|
return Array.from(result);
|
|
835
958
|
} catch (error) {
|
|
836
959
|
console.error("PostgreSQL error:", error);
|
|
960
|
+
console.error("Failed SQL:", sql);
|
|
961
|
+
console.error("Failed params:", JSON.stringify(params, null, 2));
|
|
837
962
|
throw error;
|
|
838
963
|
}
|
|
839
964
|
}
|