@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.
Files changed (59) hide show
  1. package/dist/index.es.js +287 -21
  2. package/dist/index.es.js.map +1 -1
  3. package/dist/index.umd.js +287 -21
  4. package/dist/index.umd.js.map +1 -1
  5. package/dist/server-postgresql/src/PostgresBackendDriver.d.ts +2 -1
  6. package/dist/server-postgresql/src/schema/introspect-db-inference.d.ts +5 -0
  7. package/dist/server-postgresql/src/schema/introspect-db-logic.d.ts +44 -9
  8. package/dist/server-postgresql/src/services/EntityPersistService.d.ts +9 -0
  9. package/dist/types/src/controllers/auth.d.ts +8 -2
  10. package/dist/types/src/controllers/client.d.ts +13 -0
  11. package/dist/types/src/controllers/navigation.d.ts +18 -6
  12. package/dist/types/src/controllers/registry.d.ts +9 -1
  13. package/dist/types/src/controllers/side_entity_controller.d.ts +7 -0
  14. package/dist/types/src/rebase_context.d.ts +17 -0
  15. package/dist/types/src/types/collections.d.ts +20 -1
  16. package/dist/types/src/types/component_ref.d.ts +47 -0
  17. package/dist/types/src/types/entity_views.d.ts +2 -1
  18. package/dist/types/src/types/index.d.ts +1 -0
  19. package/dist/types/src/types/properties.d.ts +15 -3
  20. package/dist/types/src/types/translations.d.ts +2 -0
  21. package/examples/sdk-demo/node_modules/esbuild/LICENSE.md +21 -0
  22. package/examples/sdk-demo/node_modules/esbuild/README.md +3 -0
  23. package/examples/sdk-demo/node_modules/esbuild/bin/esbuild +223 -0
  24. package/examples/sdk-demo/node_modules/esbuild/install.js +289 -0
  25. package/examples/sdk-demo/node_modules/esbuild/lib/main.d.ts +716 -0
  26. package/examples/sdk-demo/node_modules/esbuild/lib/main.js +2242 -0
  27. package/examples/sdk-demo/node_modules/esbuild/package.json +49 -0
  28. package/package.json +5 -5
  29. package/src/PostgresBackendDriver.ts +23 -6
  30. package/src/cli.ts +10 -2
  31. package/src/data-transformer.ts +84 -1
  32. package/src/schema/doctor.ts +14 -2
  33. package/src/schema/generate-drizzle-schema-logic.ts +52 -5
  34. package/src/schema/introspect-db-inference.ts +238 -0
  35. package/src/schema/introspect-db-logic.ts +365 -61
  36. package/src/schema/introspect-db.ts +66 -23
  37. package/src/services/EntityFetchService.ts +16 -0
  38. package/src/services/EntityPersistService.ts +88 -12
  39. package/test/generate-drizzle-schema.test.ts +295 -0
  40. package/test/introspect-db-generation.test.ts +32 -10
  41. package/test/property-ordering.test.ts +395 -0
  42. package/jest-all.log +0 -3128
  43. package/jest.log +0 -49
  44. package/scratch.ts +0 -41
  45. package/test-drizzle-bug.ts +0 -18
  46. package/test-drizzle-out/0000_cultured_freak.sql +0 -7
  47. package/test-drizzle-out/0001_tiresome_professor_monster.sql +0 -1
  48. package/test-drizzle-out/meta/0000_snapshot.json +0 -55
  49. package/test-drizzle-out/meta/0001_snapshot.json +0 -63
  50. package/test-drizzle-out/meta/_journal.json +0 -20
  51. package/test-drizzle-prompt.sh +0 -2
  52. package/test-policy-prompt.sh +0 -3
  53. package/test-programmatic.ts +0 -30
  54. package/test-programmatic2.ts +0 -59
  55. package/test-schema-no-policies.ts +0 -12
  56. package/test_drizzle_mock.js +0 -3
  57. package/test_find_changed.mjs +0 -32
  58. package/test_hash.js +0 -14
  59. package/test_output.txt +0 -3145
package/dist/index.umd.js CHANGED
@@ -132,7 +132,8 @@
132
132
  }
133
133
  const DEFAULT_ONE_OF_TYPE = "type";
134
134
  const DEFAULT_ONE_OF_VALUE = "value";
135
- const snakeCaseRegex = /[A-Z]{2,}(?=[A-Z][a-z]+[0-9]*|\b)|[A-Z]?[a-z]+[0-9]*|[A-Z]|[0-9]+/g;
135
+ const tokenizeRegex = /[A-Z]{2,}(?=[A-Z][a-z]|\b)|[A-Z]?[a-z]+|[0-9]+(?:[a-z](?![a-z]))?|[A-Z]/g;
136
+ const snakeCaseRegex = tokenizeRegex;
136
137
  const toSnakeCase = (str) => {
137
138
  const regExpMatchArray = str.match(snakeCaseRegex);
138
139
  if (!regExpMatchArray) return "";
@@ -1029,6 +1030,80 @@
1029
1030
  const singularName = snakeCaseName.endsWith("s") ? snakeCaseName.slice(0, -1) : snakeCaseName;
1030
1031
  return `${singularName}_id`;
1031
1032
  }
1033
+ function updateDateAutoValues({
1034
+ inputValues,
1035
+ properties,
1036
+ status,
1037
+ timestampNowValue
1038
+ }) {
1039
+ return traverseValuesProperties(inputValues, properties, (inputValue, property) => {
1040
+ if (property.type === "date") {
1041
+ if (status === "existing" && property.autoValue === "on_update") {
1042
+ return timestampNowValue;
1043
+ } else if ((status === "new" || status === "copy") && (property.autoValue === "on_update" || property.autoValue === "on_create")) {
1044
+ return timestampNowValue;
1045
+ } else {
1046
+ return inputValue;
1047
+ }
1048
+ } else {
1049
+ return inputValue;
1050
+ }
1051
+ }) ?? {};
1052
+ }
1053
+ function traverseValuesProperties(inputValues, properties, operation) {
1054
+ const safeInputValues = inputValues ?? {};
1055
+ const updatedValues = Object.entries(properties).map(([key, property]) => {
1056
+ const inputValue = safeInputValues && safeInputValues[key];
1057
+ const updatedValue = traverseValueProperty(inputValue, property, operation);
1058
+ if (updatedValue === null) return null;
1059
+ if (updatedValue === void 0) return void 0;
1060
+ return {
1061
+ [key]: updatedValue
1062
+ };
1063
+ }).reduce((a, b) => ({
1064
+ ...a,
1065
+ ...b
1066
+ }), {});
1067
+ const result = mergeDeep(safeInputValues, updatedValues);
1068
+ if (!result || Object.keys(result).length === 0) return void 0;
1069
+ return result;
1070
+ }
1071
+ function traverseValueProperty(inputValue, property, operation) {
1072
+ let value;
1073
+ if (property.type === "map" && property.properties) {
1074
+ value = traverseValuesProperties(inputValue, property.properties, operation);
1075
+ } else if (property.type === "array") {
1076
+ const of = property.of;
1077
+ if (of && Array.isArray(inputValue) && !Array.isArray(of)) {
1078
+ value = inputValue.map((e) => traverseValueProperty(e, of, operation));
1079
+ } else if (of && Array.isArray(inputValue) && Array.isArray(of)) {
1080
+ value = inputValue.map((e, i) => {
1081
+ if (i < of.length) return traverseValueProperty(e, of[i], operation);
1082
+ return null;
1083
+ }).filter(Boolean);
1084
+ } else if (property.oneOf && Array.isArray(inputValue)) {
1085
+ const typeField = property.oneOf?.typeField ?? DEFAULT_ONE_OF_TYPE;
1086
+ const valueField = property.oneOf?.valueField ?? DEFAULT_ONE_OF_VALUE;
1087
+ value = inputValue.map((e) => {
1088
+ if (e === null) return null;
1089
+ if (typeof e !== "object") return e;
1090
+ const rec = e;
1091
+ const type = rec[typeField];
1092
+ const childProperty = property.oneOf?.properties[type];
1093
+ if (!type || !childProperty) return e;
1094
+ return {
1095
+ [typeField]: type,
1096
+ [valueField]: traverseValueProperty(rec[valueField], childProperty, operation)
1097
+ };
1098
+ });
1099
+ } else {
1100
+ value = inputValue;
1101
+ }
1102
+ } else {
1103
+ value = operation(inputValue, property);
1104
+ }
1105
+ return value;
1106
+ }
1032
1107
  function createRelationRef(id, path2) {
1033
1108
  return {
1034
1109
  id,
@@ -2616,8 +2691,8 @@
2616
2691
  var freeExports = exports$1 && !exports$1.nodeType && exports$1;
2617
2692
  var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
2618
2693
  var moduleExports = freeModule && freeModule.exports === freeExports;
2619
- var Buffer = moduleExports ? root2.Buffer : void 0;
2620
- var nativeIsBuffer = Buffer ? Buffer.isBuffer : void 0;
2694
+ var Buffer2 = moduleExports ? root2.Buffer : void 0;
2695
+ var nativeIsBuffer = Buffer2 ? Buffer2.isBuffer : void 0;
2621
2696
  var isBuffer2 = nativeIsBuffer || stubFalse2;
2622
2697
  module2.exports = isBuffer2;
2623
2698
  })(isBuffer$2, isBuffer$2.exports);
@@ -2782,7 +2857,7 @@
2782
2857
  var freeExports = exports$1 && !exports$1.nodeType && exports$1;
2783
2858
  var freeModule = freeExports && true && module2 && !module2.nodeType && module2;
2784
2859
  var moduleExports = freeModule && freeModule.exports === freeExports;
2785
- var Buffer = moduleExports ? root2.Buffer : void 0, allocUnsafe = Buffer ? Buffer.allocUnsafe : void 0;
2860
+ var Buffer2 = moduleExports ? root2.Buffer : void 0, allocUnsafe = Buffer2 ? Buffer2.allocUnsafe : void 0;
2786
2861
  function cloneBuffer2(buffer, isDeep) {
2787
2862
  if (isDeep) {
2788
2863
  return buffer.slice();
@@ -4485,7 +4560,25 @@
4485
4560
  return result;
4486
4561
  }
4487
4562
  return value;
4563
+ case "string":
4564
+ if (typeof value === "string") {
4565
+ if (value.startsWith("data:application/octet-stream;base64,")) {
4566
+ const base64Data = value.split(",")[1];
4567
+ if (base64Data) {
4568
+ return Buffer.from(base64Data, "base64");
4569
+ }
4570
+ }
4571
+ }
4572
+ return value;
4488
4573
  default:
4574
+ if (typeof value === "string") {
4575
+ if (value.startsWith("data:application/octet-stream;base64,")) {
4576
+ const base64Data = value.split(",")[1];
4577
+ if (base64Data) {
4578
+ return Buffer.from(base64Data, "base64");
4579
+ }
4580
+ }
4581
+ }
4489
4582
  return value;
4490
4583
  }
4491
4584
  }
@@ -4611,6 +4704,37 @@
4611
4704
  return value;
4612
4705
  }
4613
4706
  switch (property.type) {
4707
+ case "string": {
4708
+ if (typeof value === "string") return value;
4709
+ let isBuffer2 = false;
4710
+ let buf = null;
4711
+ if (Buffer.isBuffer(value)) {
4712
+ isBuffer2 = true;
4713
+ buf = value;
4714
+ } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
4715
+ isBuffer2 = true;
4716
+ buf = Buffer.from(value.data);
4717
+ }
4718
+ if (isBuffer2 && buf) {
4719
+ let isPrintable = true;
4720
+ for (let i = 0; i < buf.length; i++) {
4721
+ const b = buf[i];
4722
+ if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
4723
+ isPrintable = false;
4724
+ break;
4725
+ }
4726
+ }
4727
+ return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
4728
+ }
4729
+ if (typeof value === "object" && value !== null) {
4730
+ try {
4731
+ return JSON.stringify(value);
4732
+ } catch {
4733
+ return String(value);
4734
+ }
4735
+ }
4736
+ return String(value);
4737
+ }
4614
4738
  case "relation":
4615
4739
  if (typeof value === "string" || typeof value === "number") {
4616
4740
  let relationDef = property.relation;
@@ -4694,8 +4818,29 @@
4694
4818
  }
4695
4819
  return null;
4696
4820
  }
4697
- default:
4821
+ default: {
4822
+ let isBuffer2 = false;
4823
+ let buf = null;
4824
+ if (Buffer.isBuffer(value)) {
4825
+ isBuffer2 = true;
4826
+ buf = value;
4827
+ } else if (typeof value === "object" && value !== null && value.type === "Buffer" && Array.isArray(value.data)) {
4828
+ isBuffer2 = true;
4829
+ buf = Buffer.from(value.data);
4830
+ }
4831
+ if (isBuffer2 && buf) {
4832
+ let isPrintable = true;
4833
+ for (let i = 0; i < buf.length; i++) {
4834
+ const b = buf[i];
4835
+ if ((b < 32 || b > 126) && b !== 9 && b !== 10 && b !== 13) {
4836
+ isPrintable = false;
4837
+ break;
4838
+ }
4839
+ }
4840
+ return isPrintable ? buf.toString("utf8") : `data:application/octet-stream;base64,${buf.toString("base64")}`;
4841
+ }
4698
4842
  return value;
4843
+ }
4699
4844
  }
4700
4845
  }
4701
4846
  function normalizeScalarValues(data, properties, collection, resolvedRelations, options) {
@@ -5963,6 +6108,10 @@
5963
6108
  await this.resolveJoinPathRelations(entity, collection, collectionPath, parsedId, databaseId);
5964
6109
  return entity;
5965
6110
  } catch (e) {
6111
+ if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
6112
+ console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
6113
+ 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.`);
6114
+ }
5966
6115
  console.warn(`[EntityFetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select:`, e);
5967
6116
  }
5968
6117
  }
@@ -6023,6 +6172,10 @@
6023
6172
  const entities = results2.map((row) => this.drizzleResultToEntity(row, collection, collectionPath, idInfo, options.databaseId, idInfoArray));
6024
6173
  return entities;
6025
6174
  } catch (e) {
6175
+ if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
6176
+ console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
6177
+ 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.`);
6178
+ }
6026
6179
  console.warn(`[EntityFetchService] db.query.findMany failed for ${collectionPath}, falling back to db.select:`, e);
6027
6180
  }
6028
6181
  }
@@ -6292,6 +6445,10 @@
6292
6445
  await this.resolveJoinPathRelationsBatchRest(restRows, collection, collectionPath, idInfoArray, include);
6293
6446
  return restRows;
6294
6447
  } catch (e) {
6448
+ if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
6449
+ console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
6450
+ 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.`);
6451
+ }
6295
6452
  console.warn(`[fetchCollectionForRest] db.query.findMany failed for ${collectionPath}, falling back:`, e);
6296
6453
  }
6297
6454
  }
@@ -6372,6 +6529,10 @@
6372
6529
  await this.resolveJoinPathRelationsBatchRest([restRow], collection, collectionPath, idInfoArray, include);
6373
6530
  return restRow;
6374
6531
  } catch (e) {
6532
+ if (e instanceof Error && e.message.includes("not enough information to infer relation")) {
6533
+ console.error(`[EntityFetchService] Relation inference error for collection '${collectionPath}': ${e.message}`);
6534
+ 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.`);
6535
+ }
6375
6536
  console.warn(`[fetchEntityForRest] db.query.findFirst failed for ${collectionPath}, falling back:`, e);
6376
6537
  }
6377
6538
  }
@@ -6765,22 +6926,78 @@
6765
6926
  const pgError = this.extractPgError(error);
6766
6927
  if (pgError) {
6767
6928
  const detail = pgError.detail;
6929
+ const hint = pgError.hint;
6768
6930
  const constraint = pgError.constraint;
6769
6931
  const column = pgError.column;
6770
6932
  const table = pgError.table;
6933
+ const dataType = pgError.dataType;
6934
+ const pgMessage = pgError.message || "Unknown database error";
6935
+ const suffix = hint ? ` Hint: ${hint}` : "";
6936
+ const tableRef = table ?? collectionSlug;
6771
6937
  switch (pgError.code) {
6772
6938
  case "23503":
6773
- return new Error(detail ? `Foreign key constraint violated: ${detail}` : `Cannot save: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".`);
6939
+ return new Error(detail ? `Foreign key constraint violated: ${detail}${suffix}` : `Cannot save: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
6774
6940
  case "23505":
6775
- return new Error(detail ? `Duplicate value: ${detail}` : `Cannot save: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".`);
6941
+ return new Error(detail ? `Duplicate value: ${detail}${suffix}` : `Cannot save: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
6776
6942
  case "23502":
6777
- return new Error(`Missing required field: "${column ?? "unknown"}" in "${table ?? collectionSlug}" cannot be empty.`);
6943
+ return new Error(`Missing required field: "${column ?? "unknown"}" in "${tableRef}" cannot be empty.${suffix}`);
6778
6944
  case "23514":
6779
- return new Error(`Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".`);
6945
+ return new Error(`Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${collectionSlug}".${suffix}`);
6946
+ case "22P02":
6947
+ return new Error(`Invalid data format in "${collectionSlug}": ${pgMessage}${suffix}`);
6948
+ case "22001":
6949
+ return new Error(`Value too long for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`);
6950
+ case "22003":
6951
+ return new Error(`Numeric value out of range for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`);
6952
+ case "42703":
6953
+ return new Error(`Unknown column in "${tableRef}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`);
6954
+ case "42P01":
6955
+ return new Error(`Table not found for "${collectionSlug}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`);
6956
+ default: {
6957
+ const parts = [`Database error in "${collectionSlug}" [${pgError.code}]: ${pgMessage}`];
6958
+ if (detail) parts.push(`Detail: ${detail}`);
6959
+ if (column) parts.push(`Column: ${column}`);
6960
+ if (dataType) parts.push(`Data type: ${dataType}`);
6961
+ if (constraint) parts.push(`Constraint: ${constraint}`);
6962
+ if (hint) parts.push(`Hint: ${hint}`);
6963
+ return new Error(parts.join(". "));
6964
+ }
6965
+ }
6966
+ }
6967
+ const causeMessage = this.extractCauseMessage(error);
6968
+ if (causeMessage) {
6969
+ return new Error(`Database error in "${collectionSlug}": ${causeMessage}`);
6970
+ }
6971
+ if (error instanceof Error) {
6972
+ const cleaned = this.stripSqlFromMessage(error.message, collectionSlug);
6973
+ return new Error(cleaned);
6974
+ }
6975
+ return new Error(`Database error in "${collectionSlug}": ${String(error)}`);
6976
+ }
6977
+ /**
6978
+ * Walk the error cause chain and return the deepest meaningful message.
6979
+ */
6980
+ extractCauseMessage(error) {
6981
+ if (!error || typeof error !== "object") return null;
6982
+ const err = error;
6983
+ if (err.cause && typeof err.cause === "object") {
6984
+ const deeper = this.extractCauseMessage(err.cause);
6985
+ if (deeper) return deeper;
6986
+ if (err.cause instanceof Error && err.cause.message) {
6987
+ return err.cause.message;
6780
6988
  }
6781
6989
  }
6782
- if (error instanceof Error) return error;
6783
- return new Error(String(error));
6990
+ return null;
6991
+ }
6992
+ /**
6993
+ * Strip the raw SQL query from a Drizzle "Failed query: ..." message,
6994
+ * keeping only the error description.
6995
+ */
6996
+ stripSqlFromMessage(message, collectionSlug) {
6997
+ if (message.startsWith("Failed query:")) {
6998
+ return `Failed to save entity in "${collectionSlug}". Check server logs for details.`;
6999
+ }
7000
+ return message;
6784
7001
  }
6785
7002
  /**
6786
7003
  * Extract the underlying PostgreSQL error from a Drizzle wrapper.
@@ -6789,7 +7006,7 @@
6789
7006
  extractPgError(error) {
6790
7007
  if (!error || typeof error !== "object") return null;
6791
7008
  const err = error;
6792
- if (err.code && /^[0-9]{5}$/.test(err.code)) {
7009
+ if (err.code && /^[0-9A-Z]{5}$/.test(err.code)) {
6793
7010
  return err;
6794
7011
  }
6795
7012
  if (err.cause && typeof err.cause === "object") {
@@ -7077,6 +7294,7 @@
7077
7294
  branchService;
7078
7295
  user;
7079
7296
  data;
7297
+ client;
7080
7298
  /**
7081
7299
  * When true, realtime notifications are deferred until after the
7082
7300
  * wrapping transaction commits. Set by `withAuth` → `withTransaction`.
@@ -7166,7 +7384,8 @@
7166
7384
  const contextForCallback = {
7167
7385
  user: this.user,
7168
7386
  driver: this,
7169
- data: this.data
7387
+ data: this.data,
7388
+ client: this.client
7170
7389
  };
7171
7390
  return Promise.all(entities.map(async (entity) => {
7172
7391
  let fetched = entity;
@@ -7260,7 +7479,8 @@
7260
7479
  const contextForCallback = {
7261
7480
  user: this.user,
7262
7481
  driver: this,
7263
- data: this.data
7482
+ data: this.data,
7483
+ client: this.client
7264
7484
  };
7265
7485
  if (callbacks?.afterRead) {
7266
7486
  entity = await callbacks.afterRead({
@@ -7329,7 +7549,8 @@
7329
7549
  const contextForCallback = {
7330
7550
  user: this.user,
7331
7551
  driver: this,
7332
- data: this.data
7552
+ data: this.data,
7553
+ client: this.client
7333
7554
  };
7334
7555
  let previousValuesForHistory;
7335
7556
  if (status === "existing" && entityId) {
@@ -7364,6 +7585,14 @@
7364
7585
  if (result) updatedValues = mergeDeep(updatedValues, result);
7365
7586
  }
7366
7587
  }
7588
+ if (resolvedCollection?.properties) {
7589
+ updatedValues = updateDateAutoValues({
7590
+ inputValues: updatedValues,
7591
+ properties: resolvedCollection.properties,
7592
+ status: status ?? "new",
7593
+ timestampNowValue: /* @__PURE__ */ new Date()
7594
+ });
7595
+ }
7367
7596
  try {
7368
7597
  let savedEntity = await this.entityService.saveEntity(path2, updatedValues, entityId, resolvedCollection?.databaseId);
7369
7598
  if (savedEntity && (callbacks?.afterRead || propertyCallbacks?.afterRead)) {
@@ -7469,7 +7698,8 @@
7469
7698
  const contextForCallback = {
7470
7699
  user: this.user,
7471
7700
  driver: this,
7472
- data: this.data
7701
+ data: this.data,
7702
+ client: this.client
7473
7703
  };
7474
7704
  if (callbacks?.beforeDelete || propertyCallbacks?.beforeDelete) {
7475
7705
  if (callbacks?.beforeDelete) {
@@ -7796,6 +8026,7 @@
7796
8026
  txDelegate.entityService = txEntityService;
7797
8027
  txDelegate._deferNotifications = true;
7798
8028
  txDelegate._pendingNotifications = pendingNotifications;
8029
+ txDelegate.client = this.delegate.client;
7799
8030
  return await operation(txDelegate);
7800
8031
  });
7801
8032
  for (const notification of pendingNotifications) {
@@ -8077,6 +8308,12 @@
8077
8308
  references: [users.id]
8078
8309
  })
8079
8310
  }));
8311
+ const resolveColumnName = (propName, prop) => {
8312
+ if (prop && "columnName" in prop && typeof prop.columnName === "string") {
8313
+ return prop.columnName;
8314
+ }
8315
+ return toSnakeCase(propName);
8316
+ };
8080
8317
  const getPrimaryKeyProp = (collection) => {
8081
8318
  if (collection.properties) {
8082
8319
  const idPropEntry = Object.entries(collection.properties).find(([_, prop]) => "isId" in prop && Boolean(prop.isId));
@@ -8117,7 +8354,7 @@
8117
8354
  return !hasExplicitId && propName === "id";
8118
8355
  };
8119
8356
  const getDrizzleColumn = (propName, prop, collection, collections) => {
8120
- const colName = toSnakeCase(propName);
8357
+ const colName = resolveColumnName(propName, prop);
8121
8358
  let columnDefinition;
8122
8359
  switch (prop.type) {
8123
8360
  case "string": {
@@ -8189,6 +8426,9 @@
8189
8426
  } else {
8190
8427
  columnDefinition = `timestamp("${colName}", { withTimezone: true, mode: 'string' })`;
8191
8428
  }
8429
+ if (dateProp.autoValue === "on_create" || dateProp.autoValue === "on_update") {
8430
+ columnDefinition += `.default(sql\`now()\`)`;
8431
+ }
8192
8432
  break;
8193
8433
  }
8194
8434
  case "map":
@@ -8221,7 +8461,7 @@
8221
8461
  } catch {
8222
8462
  return null;
8223
8463
  }
8224
- const fkColumnName = toSnakeCase(relation.localKey);
8464
+ const fkColumnName = relation.localKey;
8225
8465
  const targetTableVar = getTableVarName(getTableName(targetCollection));
8226
8466
  const pkProp = getPrimaryKeyProp(targetCollection);
8227
8467
  const targetIdField = pkProp.name;
@@ -8409,7 +8649,7 @@
8409
8649
  Object.entries(collection.properties ?? {}).forEach(([propName, prop]) => {
8410
8650
  if ("enum" in prop && (prop.type === "string" || prop.type === "number") && prop.enum) {
8411
8651
  const enumVarName = getEnumVarName(collectionPath, propName);
8412
- const enumDbName = `${collectionPath}_${toSnakeCase(propName)}`;
8652
+ const enumDbName = `${collectionPath}_${resolveColumnName(propName, prop)}`;
8413
8653
  const values = Array.isArray(prop.enum) ? prop.enum.map((v) => String(v.id ?? v)) : Object.keys(prop.enum);
8414
8654
  if (values.length > 0) {
8415
8655
  schemaContent += `export const ${enumVarName} = pgEnum("${enumDbName}", [${values.map((v) => `'${v}'`).join(", ")}]);
@@ -8466,9 +8706,9 @@
8466
8706
  const targetId = getPrimaryKeyName(targetCollection);
8467
8707
  schemaContent += `export const ${tableVarName} = pgTable("${tableName}", {
8468
8708
  `;
8469
- schemaContent += ` ${sourceColumn}: ${sourceColType}("${toSnakeCase(sourceColumn)}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
8709
+ schemaContent += ` ${sourceColumn}: ${sourceColType}("${sourceColumn}").notNull().references(() => ${getTableVarName(getTableName(sourceCollection))}.${sourceId}, ${refOptions}),
8470
8710
  `;
8471
- schemaContent += ` ${targetColumn}: ${targetColType}("${toSnakeCase(targetColumn)}").notNull().references(() => ${getTableVarName(getTableName(targetCollection))}.${targetId}, ${refOptions}),
8711
+ schemaContent += ` ${targetColumn}: ${targetColType}("${targetColumn}").notNull().references(() => ${getTableVarName(getTableName(targetCollection))}.${targetId}, ${refOptions}),
8472
8712
  `;
8473
8713
  schemaContent += "}, (table) => ({\n";
8474
8714
  schemaContent += ` pk: primaryKey({ columns: [table.${sourceColumn}, table.${targetColumn}] })
@@ -8592,6 +8832,32 @@
8592
8832
  console.warn(`Could not generate relation ${relationKey} for ${collection.name}:`, e);
8593
8833
  }
8594
8834
  }
8835
+ for (const otherCollection of collections) {
8836
+ if (otherCollection.slug === collection.slug) continue;
8837
+ const otherRelations = resolveCollectionRelations(otherCollection);
8838
+ for (const [otherKey, otherRel] of Object.entries(otherRelations)) {
8839
+ if (otherRel.direction === "inverse" && otherRel.foreignKeyOnTarget) {
8840
+ try {
8841
+ const otherTarget = otherRel.target();
8842
+ if (otherTarget.slug === collection.slug) {
8843
+ const drizzleRelationName = computeSharedRelationName(otherRel, otherCollection, collections);
8844
+ const deduplicationKey = `${drizzleRelationName}::owning`;
8845
+ if (!emittedRelationNames.has(deduplicationKey)) {
8846
+ const otherTableVar = getTableVarName(getTableName(otherCollection));
8847
+ const synthKey = `_synth_${otherTableVar}_${otherRel.foreignKeyOnTarget}`;
8848
+ tableRelations.push(` "${synthKey}": one(${otherTableVar}, {
8849
+ fields: [${tableVarName}.${otherRel.foreignKeyOnTarget}],
8850
+ references: [${otherTableVar}.${getPrimaryKeyName(otherCollection)}],
8851
+ relationName: "${drizzleRelationName}"
8852
+ })`);
8853
+ emittedRelationNames.add(deduplicationKey);
8854
+ }
8855
+ }
8856
+ } catch (e) {
8857
+ }
8858
+ }
8859
+ }
8860
+ }
8595
8861
  }
8596
8862
  if (tableRelations.length > 0) {
8597
8863
  const relVarName = `${tableVarName}Relations`;