@rebasepro/server-postgresql 0.0.1-canary.f81da60 → 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/dist/index.es.js +287 -21
- package/dist/index.es.js.map +1 -1
- package/dist/index.umd.js +287 -21
- package/dist/index.umd.js.map +1 -1
- package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
- package/dist/server-postgresql/src/schema/introspect-db-inference.d.ts +5 -0
- package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +44 -9
- package/dist/server-postgresql/src/services/EntityPersistService.d.ts +9 -0
- package/dist/types/src/controllers/auth.d.ts +8 -2
- package/dist/types/src/controllers/client.d.ts +13 -0
- package/dist/types/src/controllers/navigation.d.ts +18 -6
- package/dist/types/src/controllers/registry.d.ts +9 -1
- package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
- package/dist/types/src/rebase_context.d.ts +17 -0
- package/dist/types/src/types/collections.d.ts +20 -1
- package/dist/types/src/types/component_ref.d.ts +47 -0
- package/dist/types/src/types/entity_views.d.ts +2 -1
- package/dist/types/src/types/index.d.ts +1 -0
- package/dist/types/src/types/properties.d.ts +15 -3
- package/dist/types/src/types/translations.d.ts +2 -0
- package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +21 -0
- package/examples/sdk-demo/node_modules/esbuild/README.md +3 -0
- package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +223 -0
- package/examples/sdk-demo/node_modules/esbuild/install.js +289 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +716 -0
- package/examples/sdk-demo/node_modules/esbuild/lib/main.js +2242 -0
- package/examples/sdk-demo/node_modules/esbuild/package.json +49 -0
- package/package.json +5 -5
- package/src/PostgresBackendDriver.ts +23 -6
- package/src/cli.ts +10 -2
- package/src/data-transformer.ts +84 -1
- package/src/schema/doctor.ts +14 -2
- package/src/schema/generate-drizzle-schema-logic.ts +52 -5
- package/src/schema/introspect-db-inference.ts +238 -0
- package/src/schema/introspect-db-logic.ts +365 -61
- package/src/schema/introspect-db.ts +66 -23
- package/src/services/EntityFetchService.ts +16 -0
- package/src/services/EntityPersistService.ts +88 -12
- package/test/generate-drizzle-schema.test.ts +295 -0
- package/test/introspect-db-generation.test.ts +32 -10
- package/test/property-ordering.test.ts +395 -0
- package/jest-all.log +0 -3128
- package/jest.log +0 -49
- package/scratch.ts +0 -41
- package/test-drizzle-bug.ts +0 -18
- package/test-drizzle-out/0000_cultured_freak.sql +0 -7
- package/test-drizzle-out/0001_tiresome_professor_monster.sql +0 -1
- package/test-drizzle-out/meta/0000_snapshot.json +0 -55
- package/test-drizzle-out/meta/0001_snapshot.json +0 -63
- package/test-drizzle-out/meta/_journal.json +0 -20
- package/test-drizzle-prompt.sh +0 -2
- package/test-policy-prompt.sh +0 -3
- package/test-programmatic.ts +0 -30
- package/test-programmatic2.ts +0 -59
- package/test-schema-no-policies.ts +0 -12
- package/test_drizzle_mock.js +0 -3
- package/test_find_changed.mjs +0 -32
- package/test_hash.js +0 -14
- package/test_output.txt +0 -3145
package/dist/index.es.js
CHANGED
|
@@ -124,7 +124,8 @@ function getDataSourceCapabilities(driver) {
|
|
|
124
124
|
}
|
|
125
125
|
const DEFAULT_ONE_OF_TYPE = "type";
|
|
126
126
|
const DEFAULT_ONE_OF_VALUE = "value";
|
|
127
|
-
const
|
|
127
|
+
const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
|
|
128
|
+
const snakeCaseRegex = tokenizeRegex;
|
|
128
129
|
const toSnakeCase = (str) => {
|
|
129
130
|
const regExpMatchArray = str.match(snakeCaseRegex);
|
|
130
131
|
if (!regExpMatchArray) return "";
|
|
@@ -1021,6 +1022,80 @@ function generateForeignKeyName(name) {
|
|
|
1021
1022
|
const singularName = snakeCaseName.endsWith("s") ? snakeCaseName.slice(0, -1) : snakeCaseName;
|
|
1022
1023
|
return `${singularName}_id`;
|
|
1023
1024
|
}
|
|
1025
|
+
function updateDateAutoValues({
|
|
1026
|
+
inputValues,
|
|
1027
|
+
properties,
|
|
1028
|
+
status,
|
|
1029
|
+
timestampNowValue
|
|
1030
|
+
}) {
|
|
1031
|
+
return traverseValuesProperties(inputValues, properties, (inputValue, property) => {
|
|
1032
|
+
if (property.type === "date") {
|
|
1033
|
+
if (status === "existing" && property.autoValue === "on_update") {
|
|
1034
|
+
return timestampNowValue;
|
|
1035
|
+
} else if ((status === "new" || status === "copy") && (property.autoValue === "on_update" || property.autoValue === "on_create")) {
|
|
1036
|
+
return timestampNowValue;
|
|
1037
|
+
} else {
|
|
1038
|
+
return inputValue;
|
|
1039
|
+
}
|
|
1040
|
+
} else {
|
|
1041
|
+
return inputValue;
|
|
1042
|
+
}
|
|
1043
|
+
}) ?? {};
|
|
1044
|
+
}
|
|
1045
|
+
function traverseValuesProperties(inputValues, properties, operation) {
|
|
1046
|
+
const safeInputValues = inputValues ?? {};
|
|
1047
|
+
const updatedValues = Object.entries(properties).map(([key, property]) => {
|
|
1048
|
+
const inputValue = safeInputValues && safeInputValues[key];
|
|
1049
|
+
const updatedValue = traverseValueProperty(inputValue, property, operation);
|
|
1050
|
+
if (updatedValue === null) return null;
|
|
1051
|
+
if (updatedValue === void 0) return void 0;
|
|
1052
|
+
return {
|
|
1053
|
+
[key]: updatedValue
|
|
1054
|
+
};
|
|
1055
|
+
}).reduce((a, b) => ({
|
|
1056
|
+
...a,
|
|
1057
|
+
...b
|
|
1058
|
+
}), {});
|
|
1059
|
+
const result = mergeDeep(safeInputValues, updatedValues);
|
|
1060
|
+
if (!result || Object.keys(result).length === 0) return void 0;
|
|
1061
|
+
return result;
|
|
1062
|
+
}
|
|
1063
|
+
function traverseValueProperty(inputValue, property, operation) {
|
|
1064
|
+
let value;
|
|
1065
|
+
if (property.type === "map" && property.properties) {
|
|
1066
|
+
value = traverseValuesProperties(inputValue, property.properties, operation);
|
|
1067
|
+
} else if (property.type === "array") {
|
|
1068
|
+
const of = property.of;
|
|
1069
|
+
if (of && Array.isArray(inputValue) && !Array.isArray(of)) {
|
|
1070
|
+
value = inputValue.map((e) => traverseValueProperty(e, of, operation));
|
|
1071
|
+
} else if (of && Array.isArray(inputValue) && Array.isArray(of)) {
|
|
1072
|
+
value = inputValue.map((e, i) => {
|
|
1073
|
+
if (i < of.length) return traverseValueProperty(e, of[i], operation);
|
|
1074
|
+
return null;
|
|
1075
|
+
}).filter(Boolean);
|
|
1076
|
+
} else if (property.oneOf && Array.isArray(inputValue)) {
|
|
1077
|
+
const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;
|
|
1078
|
+
const valueField = property.oneOf?.valueField ?? DEFAULT_ONE_OF_VALUE;
|
|
1079
|
+
value = inputValue.map((e) => {
|
|
1080
|
+
if (e === null) return null;
|
|
1081
|
+
if (typeof e !== "object") return e;
|
|
1082
|
+
const rec = e;
|
|
1083
|
+
const type = rec[typeField];
|
|
1084
|
+
const childProperty = property.oneOf?.properties[type];
|
|
1085
|
+
if (!type || !childProperty) return e;
|
|
1086
|
+
return {
|
|
1087
|
+
[typeField]: type,
|
|
1088
|
+
[valueField]: traverseValueProperty(rec[valueField], childProperty, operation)
|
|
1089
|
+
};
|
|
1090
|
+
});
|
|
1091
|
+
} else {
|
|
1092
|
+
value = inputValue;
|
|
1093
|
+
}
|
|
1094
|
+
} else {
|
|
1095
|
+
value = operation(inputValue, property);
|
|
1096
|
+
}
|
|
1097
|
+
return value;
|
|
1098
|
+
}
|
|
1024
1099
|
function createRelationRef(id, path2) {
|
|
1025
1100
|
return {
|
|
1026
1101
|
id,
|
|
@@ -2608,8 +2683,8 @@ isBuffer$2.exports;
|
|
|
2608
2683
|
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
2609
2684
|
var freeModule = freeExports && true && module && !module.nodeType && module;
|
|
2610
2685
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
2611
|
-
var
|
|
2612
|
-
var nativeIsBuffer =
|
|
2686
|
+
var Buffer2 = moduleExports ? root2.Buffer : void 0;
|
|
2687
|
+
var nativeIsBuffer = Buffer2 ? Buffer2.isBuffer : void 0;
|
|
2613
2688
|
var isBuffer2 = nativeIsBuffer || stubFalse2;
|
|
2614
2689
|
module.exports = isBuffer2;
|
|
2615
2690
|
})(isBuffer$2, isBuffer$2.exports);
|
|
@@ -2774,7 +2849,7 @@ _cloneBuffer.exports;
|
|
|
2774
2849
|
var freeExports = exports$1 && !exports$1.nodeType && exports$1;
|
|
2775
2850
|
var freeModule = freeExports && true && module && !module.nodeType && module;
|
|
2776
2851
|
var moduleExports = freeModule && freeModule.exports === freeExports;
|
|
2777
|
-
var
|
|
2852
|
+
var Buffer2 = moduleExports ? root2.Buffer : void 0, allocUnsafe = Buffer2 ? Buffer2.allocUnsafe : void 0;
|
|
2778
2853
|
function cloneBuffer2(buffer, isDeep) {
|
|
2779
2854
|
if (isDeep) {
|
|
2780
2855
|
return buffer.slice();
|
|
@@ -4477,7 +4552,25 @@ function serializePropertyToServer(value, property) {
|
|
|
4477
4552
|
return result;
|
|
4478
4553
|
}
|
|
4479
4554
|
return value;
|
|
4555
|
+
case "string":
|
|
4556
|
+
if (typeof value === "string") {
|
|
4557
|
+
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
4558
|
+
const base64Data = value.split(",")[1];
|
|
4559
|
+
if (base64Data) {
|
|
4560
|
+
return Buffer.from(base64Data, "base64");
|
|
4561
|
+
}
|
|
4562
|
+
}
|
|
4563
|
+
}
|
|
4564
|
+
return value;
|
|
4480
4565
|
default:
|
|
4566
|
+
if (typeof value === "string") {
|
|
4567
|
+
if (value.startsWith("data:application/octet-stream;base64,")) {
|
|
4568
|
+
const base64Data = value.split(",")[1];
|
|
4569
|
+
if (base64Data) {
|
|
4570
|
+
return Buffer.from(base64Data, "base64");
|
|
4571
|
+
}
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4481
4574
|
return value;
|
|
4482
4575
|
}
|
|
4483
4576
|
}
|
|
@@ -4603,6 +4696,37 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
4603
4696
|
return value;
|
|
4604
4697
|
}
|
|
4605
4698
|
switch (property.type) {
|
|
4699
|
+
case "string": {
|
|
4700
|
+
if (typeof value === "string") return value;
|
|
4701
|
+
let isBuffer2 = false;
|
|
4702
|
+
let buf = null;
|
|
4703
|
+
if (Buffer.isBuffer(value)) {
|
|
4704
|
+
isBuffer2 = true;
|
|
4705
|
+
buf = value;
|
|
4706
|
+
} else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
|
|
4707
|
+
isBuffer2 = true;
|
|
4708
|
+
buf = Buffer.from(value.data);
|
|
4709
|
+
}
|
|
4710
|
+
if (isBuffer2 && buf) {
|
|
4711
|
+
let isPrintable = true;
|
|
4712
|
+
for (let i = 0; i < buf.length; i++) {
|
|
4713
|
+
const b = buf[i];
|
|
4714
|
+
if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
|
|
4715
|
+
isPrintable = false;
|
|
4716
|
+
break;
|
|
4717
|
+
}
|
|
4718
|
+
}
|
|
4719
|
+
return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4720
|
+
}
|
|
4721
|
+
if (typeof value === "object" && value !== null) {
|
|
4722
|
+
try {
|
|
4723
|
+
return JSON.stringify(value);
|
|
4724
|
+
} catch {
|
|
4725
|
+
return String(value);
|
|
4726
|
+
}
|
|
4727
|
+
}
|
|
4728
|
+
return String(value);
|
|
4729
|
+
}
|
|
4606
4730
|
case "relation":
|
|
4607
4731
|
if (typeof value === "string" || typeof value === "number") {
|
|
4608
4732
|
let relationDef = property.relation;
|
|
@@ -4686,8 +4810,29 @@ function parsePropertyFromServer(value, property, collection, propertyKey) {
|
|
|
4686
4810
|
}
|
|
4687
4811
|
return null;
|
|
4688
4812
|
}
|
|
4689
|
-
default:
|
|
4813
|
+
default: {
|
|
4814
|
+
let isBuffer2 = false;
|
|
4815
|
+
let buf = null;
|
|
4816
|
+
if (Buffer.isBuffer(value)) {
|
|
4817
|
+
isBuffer2 = true;
|
|
4818
|
+
buf = value;
|
|
4819
|
+
} else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
|
|
4820
|
+
isBuffer2 = true;
|
|
4821
|
+
buf = Buffer.from(value.data);
|
|
4822
|
+
}
|
|
4823
|
+
if (isBuffer2 && buf) {
|
|
4824
|
+
let isPrintable = true;
|
|
4825
|
+
for (let i = 0; i < buf.length; i++) {
|
|
4826
|
+
const b = buf[i];
|
|
4827
|
+
if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
|
|
4828
|
+
isPrintable = false;
|
|
4829
|
+
break;
|
|
4830
|
+
}
|
|
4831
|
+
}
|
|
4832
|
+
return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
|
|
4833
|
+
}
|
|
4690
4834
|
return value;
|
|
4835
|
+
}
|
|
4691
4836
|
}
|
|
4692
4837
|
}
|
|
4693
4838
|
function normalizeScalarValues(data, properties, collection, resolvedRelations, options) {
|
|
@@ -5955,6 +6100,10 @@ class EntityFetchService {
|
|
|
5955
6100
|
await this.resolveJoinPathRelations(entity, collection, collectionPath, parsedId, databaseId);
|
|
5956
6101
|
return entity;
|
|
5957
6102
|
} catch (e) {
|
|
6103
|
+
if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
|
|
6104
|
+
console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
|
|
6105
|
+
console.error(`Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.`);
|
|
6106
|
+
}
|
|
5958
6107
|
console.warn(`[EntityFetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select:`, e);
|
|
5959
6108
|
}
|
|
5960
6109
|
}
|
|
@@ -6015,6 +6164,10 @@ class EntityFetchService {
|
|
|
6015
6164
|
const entities = results2.map((row) => this.drizzleResultToEntity(row, collection, collectionPath, idInfo, options.databaseId, idInfoArray));
|
|
6016
6165
|
return entities;
|
|
6017
6166
|
} catch (e) {
|
|
6167
|
+
if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
|
|
6168
|
+
console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
|
|
6169
|
+
console.error(`Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.`);
|
|
6170
|
+
}
|
|
6018
6171
|
console.warn(`[EntityFetchService] db.query.findMany failed for ${collectionPath}, falling back to db.select:`, e);
|
|
6019
6172
|
}
|
|
6020
6173
|
}
|
|
@@ -6284,6 +6437,10 @@ class EntityFetchService {
|
|
|
6284
6437
|
await this.resolveJoinPathRelationsBatchRest(restRows, collection, collectionPath, idInfoArray, include);
|
|
6285
6438
|
return restRows;
|
|
6286
6439
|
} catch (e) {
|
|
6440
|
+
if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
|
|
6441
|
+
console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
|
|
6442
|
+
console.error(`Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.`);
|
|
6443
|
+
}
|
|
6287
6444
|
console.warn(`[fetchCollectionForRest] db.query.findMany failed for ${collectionPath}, falling back:`, e);
|
|
6288
6445
|
}
|
|
6289
6446
|
}
|
|
@@ -6364,6 +6521,10 @@ class EntityFetchService {
|
|
|
6364
6521
|
await this.resolveJoinPathRelationsBatchRest([restRow], collection, collectionPath, idInfoArray, include);
|
|
6365
6522
|
return restRow;
|
|
6366
6523
|
} catch (e) {
|
|
6524
|
+
if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
|
|
6525
|
+
console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
|
|
6526
|
+
console.error(`Hint: This usually means a relation in your drizzle schema is missing a reciprocal 'one()' or 'many()' definition. Run 'rebase schema generate' to fix this.`);
|
|
6527
|
+
}
|
|
6367
6528
|
console.warn(`[fetchEntityForRest] db.query.findFirst failed for ${collectionPath}, falling back:`, e);
|
|
6368
6529
|
}
|
|
6369
6530
|
}
|
|
@@ -6757,22 +6918,78 @@ class EntityPersistService {
|
|
|
6757
6918
|
const pgError = this.extractPgError(error);
|
|
6758
6919
|
if (pgError) {
|
|
6759
6920
|
const detail = pgError.detail;
|
|
6921
|
+
const hint = pgError.hint;
|
|
6760
6922
|
const constraint = pgError.constraint;
|
|
6761
6923
|
const column = pgError.column;
|
|
6762
6924
|
const table = pgError.table;
|
|
6925
|
+
const dataType = pgError.dataType;
|
|
6926
|
+
const pgMessage = pgError.message || "Unknown database error";
|
|
6927
|
+
const suffix = hint ? ` Hint: ${hint}` : "";
|
|
6928
|
+
const tableRef = table ?? collectionSlug;
|
|
6763
6929
|
switch (pgError.code) {
|
|
6764
6930
|
case "23503":
|
|
6765
|
-
return new Error(detail ? `Foreign key constraint violated: ${detail}` : `Cannot save: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}"
|
|
6931
|
+
return new Error(detail ? `Foreign key constraint violated: ${detail}${suffix}` : `Cannot save: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
|
|
6766
6932
|
case "23505":
|
|
6767
|
-
return new Error(detail ? `Duplicate value: ${detail}` : `Cannot save: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}"
|
|
6933
|
+
return new Error(detail ? `Duplicate value: ${detail}${suffix}` : `Cannot save: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
|
|
6768
6934
|
case "23502":
|
|
6769
|
-
return new Error(`Missing required field: "${column ?? "unknown"}" in "${
|
|
6935
|
+
return new Error(`Missing required field: "${column ?? "unknown"}" in "${tableRef}" cannot be empty.${suffix}`);
|
|
6770
6936
|
case "23514":
|
|
6771
|
-
return new Error(`Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}"
|
|
6937
|
+
return new Error(`Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
|
|
6938
|
+
case "22P02":
|
|
6939
|
+
return new Error(`Invalid data format in "${collectionSlug}": ${pgMessage}${suffix}`);
|
|
6940
|
+
case "22001":
|
|
6941
|
+
return new Error(`Value too long for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`);
|
|
6942
|
+
case "22003":
|
|
6943
|
+
return new Error(`Numeric value out of range for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`);
|
|
6944
|
+
case "42703":
|
|
6945
|
+
return new Error(`Unknown column in "${tableRef}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`);
|
|
6946
|
+
case "42P01":
|
|
6947
|
+
return new Error(`Table not found for "${collectionSlug}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`);
|
|
6948
|
+
default: {
|
|
6949
|
+
const parts = [`Database error in "${collectionSlug}" [${pgError.code}]: ${pgMessage}`];
|
|
6950
|
+
if (detail) parts.push(`Detail: ${detail}`);
|
|
6951
|
+
if (column) parts.push(`Column: ${column}`);
|
|
6952
|
+
if (dataType) parts.push(`Data type: ${dataType}`);
|
|
6953
|
+
if (constraint) parts.push(`Constraint: ${constraint}`);
|
|
6954
|
+
if (hint) parts.push(`Hint: ${hint}`);
|
|
6955
|
+
return new Error(parts.join(". "));
|
|
6956
|
+
}
|
|
6957
|
+
}
|
|
6958
|
+
}
|
|
6959
|
+
const causeMessage = this.extractCauseMessage(error);
|
|
6960
|
+
if (causeMessage) {
|
|
6961
|
+
return new Error(`Database error in "${collectionSlug}": ${causeMessage}`);
|
|
6962
|
+
}
|
|
6963
|
+
if (error instanceof Error) {
|
|
6964
|
+
const cleaned = this.stripSqlFromMessage(error.message, collectionSlug);
|
|
6965
|
+
return new Error(cleaned);
|
|
6966
|
+
}
|
|
6967
|
+
return new Error(`Database error in "${collectionSlug}": ${String(error)}`);
|
|
6968
|
+
}
|
|
6969
|
+
/**
|
|
6970
|
+
* Walk the error cause chain and return the deepest meaningful message.
|
|
6971
|
+
*/
|
|
6972
|
+
extractCauseMessage(error) {
|
|
6973
|
+
if (!error || typeof error !== "object") return null;
|
|
6974
|
+
const err = error;
|
|
6975
|
+
if (err.cause && typeof err.cause === "object") {
|
|
6976
|
+
const deeper = this.extractCauseMessage(err.cause);
|
|
6977
|
+
if (deeper) return deeper;
|
|
6978
|
+
if (err.cause instanceof Error && err.cause.message) {
|
|
6979
|
+
return err.cause.message;
|
|
6772
6980
|
}
|
|
6773
6981
|
}
|
|
6774
|
-
|
|
6775
|
-
|
|
6982
|
+
return null;
|
|
6983
|
+
}
|
|
6984
|
+
/**
|
|
6985
|
+
* Strip the raw SQL query from a Drizzle "Failed query: ..." message,
|
|
6986
|
+
* keeping only the error description.
|
|
6987
|
+
*/
|
|
6988
|
+
stripSqlFromMessage(message, collectionSlug) {
|
|
6989
|
+
if (message.startsWith("Failed query:")) {
|
|
6990
|
+
return `Failed to save entity in "${collectionSlug}". Check server logs for details.`;
|
|
6991
|
+
}
|
|
6992
|
+
return message;
|
|
6776
6993
|
}
|
|
6777
6994
|
/**
|
|
6778
6995
|
* Extract the underlying PostgreSQL error from a Drizzle wrapper.
|
|
@@ -6781,7 +6998,7 @@ class EntityPersistService {
|
|
|
6781
6998
|
extractPgError(error) {
|
|
6782
6999
|
if (!error || typeof error !== "object") return null;
|
|
6783
7000
|
const err = error;
|
|
6784
|
-
if (err.code && /^[0-
|
|
7001
|
+
if (err.code && /^[0-9A-Z]{5}$/.test(err.code)) {
|
|
6785
7002
|
return err;
|
|
6786
7003
|
}
|
|
6787
7004
|
if (err.cause && typeof err.cause === "object") {
|
|
@@ -7069,6 +7286,7 @@ class PostgresBackendDriver {
|
|
|
7069
7286
|
branchService;
|
|
7070
7287
|
user;
|
|
7071
7288
|
data;
|
|
7289
|
+
client;
|
|
7072
7290
|
/**
|
|
7073
7291
|
* When true, realtime notifications are deferred until after the
|
|
7074
7292
|
* wrapping transaction commits. Set by `withAuth` → `withTransaction`.
|
|
@@ -7158,7 +7376,8 @@ class PostgresBackendDriver {
|
|
|
7158
7376
|
const contextForCallback = {
|
|
7159
7377
|
user: this.user,
|
|
7160
7378
|
driver: this,
|
|
7161
|
-
data: this.data
|
|
7379
|
+
data: this.data,
|
|
7380
|
+
client: this.client
|
|
7162
7381
|
};
|
|
7163
7382
|
return Promise.all(entities.map(async (entity) => {
|
|
7164
7383
|
let fetched = entity;
|
|
@@ -7252,7 +7471,8 @@ class PostgresBackendDriver {
|
|
|
7252
7471
|
const contextForCallback = {
|
|
7253
7472
|
user: this.user,
|
|
7254
7473
|
driver: this,
|
|
7255
|
-
data: this.data
|
|
7474
|
+
data: this.data,
|
|
7475
|
+
client: this.client
|
|
7256
7476
|
};
|
|
7257
7477
|
if (callbacks?.afterRead) {
|
|
7258
7478
|
entity = await callbacks.afterRead({
|
|
@@ -7321,7 +7541,8 @@ class PostgresBackendDriver {
|
|
|
7321
7541
|
const contextForCallback = {
|
|
7322
7542
|
user: this.user,
|
|
7323
7543
|
driver: this,
|
|
7324
|
-
data: this.data
|
|
7544
|
+
data: this.data,
|
|
7545
|
+
client: this.client
|
|
7325
7546
|
};
|
|
7326
7547
|
let previousValuesForHistory;
|
|
7327
7548
|
if (status === "existing" && entityId) {
|
|
@@ -7356,6 +7577,14 @@ class PostgresBackendDriver {
|
|
|
7356
7577
|
if (result) updatedValues = mergeDeep(updatedValues, result);
|
|
7357
7578
|
}
|
|
7358
7579
|
}
|
|
7580
|
+
if (resolvedCollection?.properties) {
|
|
7581
|
+
updatedValues = updateDateAutoValues({
|
|
7582
|
+
inputValues: updatedValues,
|
|
7583
|
+
properties: resolvedCollection.properties,
|
|
7584
|
+
status: status ?? "new",
|
|
7585
|
+
timestampNowValue: /* @__PURE__ */ new Date()
|
|
7586
|
+
});
|
|
7587
|
+
}
|
|
7359
7588
|
try {
|
|
7360
7589
|
let savedEntity = await this.entityService.saveEntity(path2, updatedValues, entityId, resolvedCollection?.databaseId);
|
|
7361
7590
|
if (savedEntity && (callbacks?.afterRead || propertyCallbacks?.afterRead)) {
|
|
@@ -7461,7 +7690,8 @@ class PostgresBackendDriver {
|
|
|
7461
7690
|
const contextForCallback = {
|
|
7462
7691
|
user: this.user,
|
|
7463
7692
|
driver: this,
|
|
7464
|
-
data: this.data
|
|
7693
|
+
data: this.data,
|
|
7694
|
+
client: this.client
|
|
7465
7695
|
};
|
|
7466
7696
|
if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
|
|
7467
7697
|
if (callbacks?.beforeDelete) {
|
|
@@ -7788,6 +8018,7 @@ class AuthenticatedPostgresBackendDriver {
|
|
|
7788
8018
|
txDelegate.entityService = txEntityService;
|
|
7789
8019
|
txDelegate._deferNotifications = true;
|
|
7790
8020
|
txDelegate._pendingNotifications = pendingNotifications;
|
|
8021
|
+
txDelegate.client = this.delegate.client;
|
|
7791
8022
|
return await operation(txDelegate);
|
|
7792
8023
|
});
|
|
7793
8024
|
for (const notification of pendingNotifications) {
|
|
@@ -8069,6 +8300,12 @@ const userIdentitiesRelations = relations(userIdentities, ({
|
|
|
8069
8300
|
references: [users.id]
|
|
8070
8301
|
})
|
|
8071
8302
|
}));
|
|
8303
|
+
const resolveColumnName = (propName, prop) => {
|
|
8304
|
+
if (prop && "columnName" in prop && typeof prop.columnName === "string") {
|
|
8305
|
+
return prop.columnName;
|
|
8306
|
+
}
|
|
8307
|
+
return toSnakeCase(propName);
|
|
8308
|
+
};
|
|
8072
8309
|
const getPrimaryKeyProp = (collection) => {
|
|
8073
8310
|
if (collection.properties) {
|
|
8074
8311
|
const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => "isId" in prop && Boolean(prop.isId));
|
|
@@ -8109,7 +8346,7 @@ const isIdProperty = (propName, prop, collection) => {
|
|
|
8109
8346
|
return !hasExplicitId && propName === "id";
|
|
8110
8347
|
};
|
|
8111
8348
|
const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
8112
|
-
const colName =
|
|
8349
|
+
const colName = resolveColumnName(propName, prop);
|
|
8113
8350
|
let columnDefinition;
|
|
8114
8351
|
switch (prop.type) {
|
|
8115
8352
|
case "string": {
|
|
@@ -8181,6 +8418,9 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
8181
8418
|
} else {
|
|
8182
8419
|
columnDefinition = `timestamp("${colName}", { withTimezone: true, mode: 'string' })`;
|
|
8183
8420
|
}
|
|
8421
|
+
if (dateProp.autoValue === "on_create" || dateProp.autoValue === "on_update") {
|
|
8422
|
+
columnDefinition += `.default(sql\`now()\`)`;
|
|
8423
|
+
}
|
|
8184
8424
|
break;
|
|
8185
8425
|
}
|
|
8186
8426
|
case "map":
|
|
@@ -8213,7 +8453,7 @@ const getDrizzleColumn = (propName, prop, collection, collections) => {
|
|
|
8213
8453
|
} catch {
|
|
8214
8454
|
return null;
|
|
8215
8455
|
}
|
|
8216
|
-
const fkColumnName =
|
|
8456
|
+
const fkColumnName = relation.localKey;
|
|
8217
8457
|
const targetTableVar = getTableVarName(getTableName(targetCollection));
|
|
8218
8458
|
const pkProp = getPrimaryKeyProp(targetCollection);
|
|
8219
8459
|
const targetIdField = pkProp.name;
|
|
@@ -8401,7 +8641,7 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
8401
8641
|
Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
|
|
8402
8642
|
if ("enum" in prop && (prop.type === "string" || prop.type === "number") && prop.enum) {
|
|
8403
8643
|
const enumVarName = getEnumVarName(collectionPath, propName);
|
|
8404
|
-
const enumDbName = `${collectionPath}_${
|
|
8644
|
+
const enumDbName = `${collectionPath}_${resolveColumnName(propName, prop)}`;
|
|
8405
8645
|
const values = Array.isArray(prop.enum) ? prop.enum.map((v) => String(v.id ?? v)) : Object.keys(prop.enum);
|
|
8406
8646
|
if (values.length > 0) {
|
|
8407
8647
|
schemaContent += `export const ${enumVarName} = pgEnum("${enumDbName}", [${values.map((v) => `'${v}'`).join(", ")}]);
|
|
@@ -8458,9 +8698,9 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
8458
8698
|
const targetId = getPrimaryKeyName(targetCollection);
|
|
8459
8699
|
schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
|
|
8460
8700
|
`;
|
|
8461
|
-
schemaContent += ` ${sourceColumn}: ${sourceColType}("${
|
|
8701
|
+
schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
|
|
8462
8702
|
`;
|
|
8463
|
-
schemaContent += ` ${targetColumn}: ${targetColType}("${
|
|
8703
|
+
schemaContent += ` ${targetColumn}: ${targetColType}("${targetColumn}").notNull().references(() => ${getTableVarName(getTableName(targetCollection))}.${targetId}, ${refOptions}),
|
|
8464
8704
|
`;
|
|
8465
8705
|
schemaContent += "}, (table) => ({\n";
|
|
8466
8706
|
schemaContent += ` pk: primaryKey({ columns: [table.${sourceColumn}, table.${targetColumn}] })
|
|
@@ -8584,6 +8824,32 @@ const generateSchema = async (collections, stripPolicies = false) => {
|
|
|
8584
8824
|
console.warn(`Could not generate relation ${relationKey} for ${collection.name}:`, e);
|
|
8585
8825
|
}
|
|
8586
8826
|
}
|
|
8827
|
+
for (const otherCollection of collections) {
|
|
8828
|
+
if (otherCollection.slug === collection.slug) continue;
|
|
8829
|
+
const otherRelations = resolveCollectionRelations(otherCollection);
|
|
8830
|
+
for (const [otherKey, otherRel] of Object.entries(otherRelations)) {
|
|
8831
|
+
if (otherRel.direction === "inverse" && otherRel.foreignKeyOnTarget) {
|
|
8832
|
+
try {
|
|
8833
|
+
const otherTarget = otherRel.target();
|
|
8834
|
+
if (otherTarget.slug === collection.slug) {
|
|
8835
|
+
const drizzleRelationName = computeSharedRelationName(otherRel, otherCollection, collections);
|
|
8836
|
+
const deduplicationKey = `${drizzleRelationName}::owning`;
|
|
8837
|
+
if (!emittedRelationNames.has(deduplicationKey)) {
|
|
8838
|
+
const otherTableVar = getTableVarName(getTableName(otherCollection));
|
|
8839
|
+
const synthKey = `_synth_${otherTableVar}_${otherRel.foreignKeyOnTarget}`;
|
|
8840
|
+
tableRelations.push(` "${synthKey}": one(${otherTableVar}, {
|
|
8841
|
+
fields: [${tableVarName}.${otherRel.foreignKeyOnTarget}],
|
|
8842
|
+
references: [${otherTableVar}.${getPrimaryKeyName(otherCollection)}],
|
|
8843
|
+
relationName: "${drizzleRelationName}"
|
|
8844
|
+
})`);
|
|
8845
|
+
emittedRelationNames.add(deduplicationKey);
|
|
8846
|
+
}
|
|
8847
|
+
}
|
|
8848
|
+
} catch (e) {
|
|
8849
|
+
}
|
|
8850
|
+
}
|
|
8851
|
+
}
|
|
8852
|
+
}
|
|
8587
8853
|
}
|
|
8588
8854
|
if (tableRelations.length > 0) {
|
|
8589
8855
|
const relVarName = `${tableVarName}Relations`;
|