@rebasepro/server-postgres 0.12.1-canary.gdd7172e → 0.12.1-canary.gddbd489

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 CHANGED
@@ -2,7 +2,7 @@ import { createRequire as __createRequire } from "module";
2
2
  import process from "process";
3
3
  __createRequire(import.meta.url);
4
4
  import { a as guardPoolAgainstDirtyRelease, i as createReadReplicaConnection, n as createDirectDatabaseConnection, o as pinSearchPath, r as createPostgresDatabaseConnection } from "./connection-BuZ97wsr.js";
5
- import { C as updateDateAutoValues, D as mergeDeep, E as getPolicyNamesForRule, M as hasForeignKeyOnTarget, N as isManyToMany, O as camelCase, P as Vector, S as normalizeToEntityRelation, _ as buildCompositeId, a as getJunctionSecurityRules, b as createRelationRef, c as policyToPostgres, d as findRelation, f as getColumnName, g as resolveCollectionRelations, h as getTableVarName, i as getJunctionCollectionConfig, k as toSnakeCase, l as securityRuleToConditions, m as getTableName$1, n as CollectionRegistry, o as resolveJunctionSpecs, p as getEnumVarName, r as resolveStringColumnLength, s as getEffectiveSecurityRules, t as buildSdkData, u as findAnonymousGrants, v as getDeclaredPrimaryKeys, w as generateForeignKeyName, x as createRelationRefWithData, y as parseIdValues } from "./src-CzbghKwf.js";
5
+ import { A as toSnakeCase, C as normalizeToEntityRelation, D as getPolicyNamesForRule, F as Vector, N as hasForeignKeyOnTarget, O as mergeDeep, P as isManyToMany, S as createRelationRefWithData, T as generateForeignKeyName, _ as buildCompositeId, a as getJunctionSecurityRules, b as parseIdValues, c as policyToPostgres, d as findRelation, f as getColumnName, g as resolveCollectionRelations, h as getTableVarName, i as getJunctionCollectionConfig, k as camelCase, l as securityRuleToConditions, m as getTableName$1, n as CollectionRegistry, o as resolveJunctionSpecs, p as getEnumVarName, r as resolveStringColumnLength, s as getEffectiveSecurityRules, t as buildSdkData, u as findAnonymousGrants, v as getDeclaredPrimaryKeys, w as updateDateAutoValues, x as createRelationRef, y as isAddressableId } from "./src-DlPBctw_.js";
6
6
  import { n as isPostgresCollectionConfig, r as isRelationalCollectionConfig } from "./src-DoU9yPqq.js";
7
7
  import { t as ANONYMOUS_USER_ID } from "./policy-CeA1JcxP.js";
8
8
  import { t as createPostgresWebSocket } from "./websocket-B2LsrINK.js";
@@ -228,6 +228,27 @@ function getColumnMeta(col) {
228
228
  primary: typeof raw.primary === "boolean" ? raw.primary : void 0
229
229
  };
230
230
  }
231
+ /**
232
+ * Whether an address could name a row in this table, judged by the columns.
233
+ *
234
+ * {@link getPrimaryKeys} lets a config's `isId: "uuid"` win over the schema, so
235
+ * its `isUUID` is a claim rather than a fact — right for deriving addresses,
236
+ * wrong for refusing a query. Here the Drizzle column type decides, because it
237
+ * is what Postgres will enforce: a `uuid` column meets `/c/products/new` with
238
+ * `22P02`, which aborts the surrounding transaction and turns every later
239
+ * statement into an unrelated-looking `25P02`.
240
+ */
241
+ function idCanAddressTable(id, table, idInfoArray) {
242
+ return isAddressableId(id, idInfoArray.map((info) => {
243
+ const col = table[info.fieldName];
244
+ const meta = col ? getColumnMeta(col) : void 0;
245
+ if (!meta?.columnType) return info;
246
+ return {
247
+ ...info,
248
+ isUUID: meta.columnType === "PgUUID"
249
+ };
250
+ }));
251
+ }
231
252
  function getCollectionByPath(collectionPath, registry) {
232
253
  const collection = registry.getCollectionByPath(collectionPath);
233
254
  if (!collection) {
@@ -2772,6 +2793,247 @@ function assertWritableThrough(hop, path) {
2772
2793
  throw ApiError.badRequest(`"${path}" ends in the to-one relation '${hop.relationKey}', which cannot be written through: the foreign key for a to-one relation lives on '${hop.parentCollection.slug}', not on '${hop.targetCollection.slug}'. Write the target row at "${hop.targetCollection.slug}" and set '${hop.relationKey}' on the parent instead.`, "RELATION_NOT_WRITABLE");
2773
2794
  }
2774
2795
  //#endregion
2796
+ //#region src/utils/pg-error-utils.ts
2797
+ /**
2798
+ * Shared PostgreSQL error extraction and user-friendly message formatting.
2799
+ *
2800
+ * Drizzle wraps native PG errors in a `.cause` chain. These utilities
2801
+ * unwrap that chain to get the real PostgreSQL error (identified by a
2802
+ * 5-character alphanumeric `code` such as `42P01`) and translate it into
2803
+ * a message that is safe and helpful to show to end-users.
2804
+ */
2805
+ /**
2806
+ * Return the error when it is a deliberate 4xx, otherwise null.
2807
+ *
2808
+ * A thrown `ApiError` is a decision the server made about the request, not a
2809
+ * database failure: its message and code are already written for the client.
2810
+ */
2811
+ function asClientFacingError(error) {
2812
+ if (!(error instanceof Error)) return null;
2813
+ const e = error;
2814
+ if (typeof e.statusCode !== "number" || e.statusCode < 400 || e.statusCode >= 500) return null;
2815
+ return e;
2816
+ }
2817
+ /**
2818
+ * Extract the underlying PostgreSQL error from a Drizzle wrapper.
2819
+ * Drizzle wraps PG errors in a `cause` property — this function
2820
+ * recursively walks the chain until it finds an object with a PG
2821
+ * error code (5-char alphanumeric, e.g. `42P01`).
2822
+ */
2823
+ function extractPgError(error) {
2824
+ if (!error || typeof error !== "object") return null;
2825
+ if (!(error instanceof Error)) {
2826
+ if ("cause" in error && error.cause && typeof error.cause === "object") return extractPgError(error.cause);
2827
+ return null;
2828
+ }
2829
+ if ("code" in error && typeof error.code === "string" && /^[0-9A-Z]{5}$/.test(error.code)) return error;
2830
+ if (error.cause && typeof error.cause === "object") return extractPgError(error.cause);
2831
+ return null;
2832
+ }
2833
+ /**
2834
+ * Whether the failure came back from Postgres rather than from building the
2835
+ * query — which decides whether a fallback query is worth issuing.
2836
+ *
2837
+ * Reads here run inside a transaction (that is where `SET LOCAL ROLE` binds
2838
+ * RLS). Once a statement raises, that transaction is aborted, and every later
2839
+ * statement on it returns `25P02` — "current transaction is aborted, commands
2840
+ * ignored until end of transaction block". So a retry after a database error
2841
+ * cannot succeed, and it replaces a precise diagnosis ("invalid input syntax
2842
+ * for type uuid") with a generic one. Rethrow instead.
2843
+ *
2844
+ * A query the driver could not even build — a missing reciprocal relation, say
2845
+ * — never reached Postgres, leaves the transaction usable, and is exactly what
2846
+ * the fallback paths exist for.
2847
+ */
2848
+ function reachedDatabase(error) {
2849
+ return extractPgError(error) !== null;
2850
+ }
2851
+ /**
2852
+ * Walk the error cause chain and return the deepest meaningful message.
2853
+ */
2854
+ function extractCauseMessage(error) {
2855
+ if (!error || typeof error !== "object") return null;
2856
+ if (!(error instanceof Error)) return null;
2857
+ if (error.cause && typeof error.cause === "object") {
2858
+ const deeper = extractCauseMessage(error.cause);
2859
+ if (deeper) return deeper;
2860
+ if (error.cause instanceof Error && error.cause.message) return error.cause.message;
2861
+ }
2862
+ return null;
2863
+ }
2864
+ /**
2865
+ * Codes that mean "this connection will never work as configured".
2866
+ *
2867
+ * A wrong password or a database that does not exist is a settled fact about
2868
+ * the connection string, not a transient fault — retrying produces the same
2869
+ * answer forever.
2870
+ */
2871
+ var UNRECOVERABLE_CONNECT_CODES = /* @__PURE__ */ new Set([
2872
+ "28P01",
2873
+ "28000",
2874
+ "3D000",
2875
+ "42501"
2876
+ ]);
2877
+ /**
2878
+ * Describe a failed connection attempt in terms a developer can act on.
2879
+ *
2880
+ * The error a caller catches is Drizzle's wrapper: its message is
2881
+ * `Failed query: SELECT 1` and its stack runs through drizzle internals, while
2882
+ * the sentence that says what is actually wrong — "password authentication
2883
+ * failed for user …", "database … does not exist" — sits in `.cause`. Logging
2884
+ * the wrapper, as the bootstrapper used to, tells a developer with a typo in
2885
+ * their `DATABASE_URL` nothing at all.
2886
+ */
2887
+ function classifyConnectFailure(error) {
2888
+ const pgError = extractPgError(error);
2889
+ const reason = pgError?.message ?? extractCauseMessage(error) ?? (error instanceof Error ? error.message : String(error));
2890
+ return {
2891
+ fatal: Boolean(pgError?.code && UNRECOVERABLE_CONNECT_CODES.has(pgError.code)),
2892
+ reason,
2893
+ code: pgError?.code
2894
+ };
2895
+ }
2896
+ /**
2897
+ * Detect whether an error is specifically a role-switching permission failure
2898
+ * (e.g. "permission denied to set role" or "must be member of role"),
2899
+ * as opposed to a table-level permission denial.
2900
+ *
2901
+ * This is used by the backend driver to auto-disable role switching when the
2902
+ * connection user lacks SET ROLE privileges, rather than surfacing a confusing
2903
+ * error to the Studio SQL Editor user.
2904
+ */
2905
+ function isRoleSwitchingPermissionError(error) {
2906
+ const pgError = extractPgError(error);
2907
+ if (!pgError || pgError.code !== "42501") return false;
2908
+ const msg = pgError.message.toLowerCase();
2909
+ return msg.includes("set role") || msg.includes("member of role");
2910
+ }
2911
+ /**
2912
+ * Translate a raw PostgreSQL error into a user-friendly message.
2913
+ *
2914
+ * @param pgError - The extracted PostgreSQL error (from {@link extractPgError})
2915
+ * @param context - A human-readable context string (e.g. collection slug or path)
2916
+ * @returns An object with a `message` safe for the client and the PG `code`.
2917
+ */
2918
+ function pgErrorToFriendlyMessage(pgError, context) {
2919
+ const detail = pgError.detail;
2920
+ const hint = pgError.hint;
2921
+ const constraint = pgError.constraint;
2922
+ const column = pgError.column;
2923
+ const table = pgError.table;
2924
+ const dataType = pgError.dataType;
2925
+ const pgMessage = pgError.message || "Unknown database error";
2926
+ const code = pgError.code || "UNKNOWN";
2927
+ const suffix = hint ? ` Hint: ${hint}` : "";
2928
+ const tableRef = table ?? context;
2929
+ switch (pgError.code) {
2930
+ case "23503": return {
2931
+ message: detail ? `Foreign key constraint violated: ${detail}${suffix}` : `Cannot complete operation: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
2932
+ code
2933
+ };
2934
+ case "23505": return {
2935
+ message: detail ? `Duplicate value: ${detail}${suffix}` : `Cannot complete operation: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
2936
+ code
2937
+ };
2938
+ case "23502": return {
2939
+ message: `Missing required field: "${column ?? "unknown"}" in "${tableRef}" cannot be empty.${suffix}`,
2940
+ code
2941
+ };
2942
+ case "23514": return {
2943
+ message: `Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
2944
+ code
2945
+ };
2946
+ case "22P02": return {
2947
+ message: `Invalid data format in "${context}": ${pgMessage}${suffix}`,
2948
+ code
2949
+ };
2950
+ case "22001": return {
2951
+ message: `Value too long for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`,
2952
+ code
2953
+ };
2954
+ case "22003": return {
2955
+ message: `Numeric value out of range for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`,
2956
+ code
2957
+ };
2958
+ case "42703": return {
2959
+ message: `Unknown column in "${tableRef}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,
2960
+ code
2961
+ };
2962
+ case "42P01": return {
2963
+ message: `Table not found for "${context}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,
2964
+ code
2965
+ };
2966
+ case "42501": return {
2967
+ message: `Permission denied on "${tableRef}". Check your database credentials and RLS policies.${suffix}`,
2968
+ code
2969
+ };
2970
+ case "28000": return {
2971
+ message: `Authorization failed for "${context}". Check your database credentials.${suffix}`,
2972
+ code
2973
+ };
2974
+ default: {
2975
+ const parts = [`Database error in "${context}" [${code}]: ${pgMessage}`];
2976
+ if (detail) parts.push(`Detail: ${detail}`);
2977
+ if (column) parts.push(`Column: ${column}`);
2978
+ if (dataType) parts.push(`Data type: ${dataType}`);
2979
+ if (constraint) parts.push(`Constraint: ${constraint}`);
2980
+ if (hint) parts.push(`Hint: ${hint}`);
2981
+ return {
2982
+ message: parts.join(". "),
2983
+ code
2984
+ };
2985
+ }
2986
+ }
2987
+ }
2988
+ /**
2989
+ * Sanitize any error into a message safe and helpful for the client.
2990
+ *
2991
+ * A deliberate 4xx (`ApiError`) passes through untouched — the server already
2992
+ * decided what the client should read. Otherwise the PG error is extracted
2993
+ * from the Drizzle cause chain, falling back to a generic message that
2994
+ * doesn't leak SQL.
2995
+ *
2996
+ * @param error - The raw caught error
2997
+ * @param context - A human-readable context string (e.g. collection path)
2998
+ * @returns An object with `message` (user-friendly) and optional `code`
2999
+ * (the `ApiError` code, or the PG SQLSTATE).
3000
+ */
3001
+ function sanitizeErrorForClient(error, context) {
3002
+ const clientError = asClientFacingError(error);
3003
+ if (clientError) {
3004
+ const line = `[API ${clientError.statusCode} ${clientError.code ?? "BAD_REQUEST"}] in "${context}": ${clientError.message}`;
3005
+ if (clientError.expected) logger.debug(line);
3006
+ else logger.warn(`⚠️ ${line}`);
3007
+ return {
3008
+ message: clientError.message,
3009
+ ...clientError.code && { code: clientError.code }
3010
+ };
3011
+ }
3012
+ const pgError = extractPgError(error);
3013
+ if (pgError) {
3014
+ logger.error(`[PG ${pgError.code}] Error in "${context}"`, {
3015
+ code: pgError.code,
3016
+ message: pgError.message,
3017
+ detail: pgError.detail,
3018
+ hint: pgError.hint,
3019
+ column: pgError.column,
3020
+ table: pgError.table,
3021
+ constraint: pgError.constraint,
3022
+ dataType: pgError.dataType,
3023
+ drizzleMessage: error instanceof Error ? error.message : String(error)
3024
+ });
3025
+ return pgErrorToFriendlyMessage(pgError, context);
3026
+ }
3027
+ logger.error(`Database error in "${context}" (no PG error extracted)`, {
3028
+ error: error instanceof Error ? error.message : String(error),
3029
+ stack: error instanceof Error ? error.stack : void 0,
3030
+ cause: error instanceof Error && error.cause ? error.cause instanceof Error ? error.cause.message : String(error.cause) : void 0
3031
+ });
3032
+ const causeMessage = extractCauseMessage(error);
3033
+ if (causeMessage) return { message: `Database error in "${context}": ${causeMessage}` };
3034
+ return { message: `Could not load data for "${context}". Check server logs for details.` };
3035
+ }
3036
+ //#endregion
2775
3037
  //#region src/services/FetchService.ts
2776
3038
  /**
2777
3039
  * Service for handling all row read operations.
@@ -3086,6 +3348,7 @@ var FetchService = class {
3086
3348
  const idInfo = idInfoArray[0];
3087
3349
  const idField = table[idInfo.fieldName];
3088
3350
  if (!idField) throw new Error(`ID field '${idInfo.fieldName}' not found in table for collection '${collectionPath}'`);
3351
+ if (!idCanAddressTable(id, table, idInfoArray)) return void 0;
3089
3352
  const parsedId = parseIdValues(id, idInfoArray)[idInfo.fieldName];
3090
3353
  const tableName = getTableName(table);
3091
3354
  const qb = this.getQueryBuilder(tableName);
@@ -3104,6 +3367,7 @@ var FetchService = class {
3104
3367
  logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);
3105
3368
  logger.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.");
3106
3369
  }
3370
+ if (reachedDatabase(e)) throw e;
3107
3371
  logger.warn(`[FetchService] db.query.findFirst failed for ${collectionPath}, falling back to db.select`, { error: e });
3108
3372
  }
3109
3373
  const result = await this.db.select().from(table).where(eq(idField, parsedId)).limit(1);
@@ -3157,6 +3421,7 @@ var FetchService = class {
3157
3421
  logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);
3158
3422
  logger.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.");
3159
3423
  }
3424
+ if (reachedDatabase(e)) throw e;
3160
3425
  logger.warn(`[FetchService] db.query.findMany failed for ${collectionPath}, falling back to db.select`, { error: e });
3161
3426
  }
3162
3427
  let vectorMeta;
@@ -3376,6 +3641,7 @@ var FetchService = class {
3376
3641
  logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);
3377
3642
  logger.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.");
3378
3643
  }
3644
+ if (reachedDatabase(e)) throw e;
3379
3645
  logger.warn(`[fetchCollectionForRest] db.query.findMany failed for ${collectionPath}, falling back`, { error: e });
3380
3646
  }
3381
3647
  const rows = await this.fetchRowsWithConditionsRaw(collectionPath, options);
@@ -3421,6 +3687,7 @@ var FetchService = class {
3421
3687
  const idInfoArray = requirePrimaryKeys(collection, this.registry);
3422
3688
  const idInfo = idInfoArray[0];
3423
3689
  const idField = table[idInfo.fieldName];
3690
+ if (!idCanAddressTable(id, table, idInfoArray)) return null;
3424
3691
  const parsedId = parseIdValues(id, idInfoArray)[idInfo.fieldName];
3425
3692
  const tableName = getTableName(table);
3426
3693
  const qb = this.getQueryBuilder(tableName);
@@ -3439,6 +3706,7 @@ var FetchService = class {
3439
3706
  logger.error(`[FetchService] ResolvedRelation inference error for collection '${collectionPath}': ${e.message}`);
3440
3707
  logger.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.");
3441
3708
  }
3709
+ if (reachedDatabase(e)) throw e;
3442
3710
  logger.warn(`[fetchOneForRest] db.query.findFirst failed for ${collectionPath}, falling back`, { error: e });
3443
3711
  }
3444
3712
  const result = await this.db.select().from(table).where(eq(idField, parsedId)).limit(1);
@@ -3597,229 +3865,6 @@ var FetchService = class {
3597
3865
  }
3598
3866
  };
3599
3867
  //#endregion
3600
- //#region src/utils/pg-error-utils.ts
3601
- /**
3602
- * Shared PostgreSQL error extraction and user-friendly message formatting.
3603
- *
3604
- * Drizzle wraps native PG errors in a `.cause` chain. These utilities
3605
- * unwrap that chain to get the real PostgreSQL error (identified by a
3606
- * 5-character alphanumeric `code` such as `42P01`) and translate it into
3607
- * a message that is safe and helpful to show to end-users.
3608
- */
3609
- /**
3610
- * Return the error when it is a deliberate 4xx, otherwise null.
3611
- *
3612
- * A thrown `ApiError` is a decision the server made about the request, not a
3613
- * database failure: its message and code are already written for the client.
3614
- */
3615
- function asClientFacingError(error) {
3616
- if (!(error instanceof Error)) return null;
3617
- const e = error;
3618
- if (typeof e.statusCode !== "number" || e.statusCode < 400 || e.statusCode >= 500) return null;
3619
- return e;
3620
- }
3621
- /**
3622
- * Extract the underlying PostgreSQL error from a Drizzle wrapper.
3623
- * Drizzle wraps PG errors in a `cause` property — this function
3624
- * recursively walks the chain until it finds an object with a PG
3625
- * error code (5-char alphanumeric, e.g. `42P01`).
3626
- */
3627
- function extractPgError(error) {
3628
- if (!error || typeof error !== "object") return null;
3629
- if (!(error instanceof Error)) {
3630
- if ("cause" in error && error.cause && typeof error.cause === "object") return extractPgError(error.cause);
3631
- return null;
3632
- }
3633
- if ("code" in error && typeof error.code === "string" && /^[0-9A-Z]{5}$/.test(error.code)) return error;
3634
- if (error.cause && typeof error.cause === "object") return extractPgError(error.cause);
3635
- return null;
3636
- }
3637
- /**
3638
- * Walk the error cause chain and return the deepest meaningful message.
3639
- */
3640
- function extractCauseMessage(error) {
3641
- if (!error || typeof error !== "object") return null;
3642
- if (!(error instanceof Error)) return null;
3643
- if (error.cause && typeof error.cause === "object") {
3644
- const deeper = extractCauseMessage(error.cause);
3645
- if (deeper) return deeper;
3646
- if (error.cause instanceof Error && error.cause.message) return error.cause.message;
3647
- }
3648
- return null;
3649
- }
3650
- /**
3651
- * Codes that mean "this connection will never work as configured".
3652
- *
3653
- * A wrong password or a database that does not exist is a settled fact about
3654
- * the connection string, not a transient fault — retrying produces the same
3655
- * answer forever.
3656
- */
3657
- var UNRECOVERABLE_CONNECT_CODES = /* @__PURE__ */ new Set([
3658
- "28P01",
3659
- "28000",
3660
- "3D000",
3661
- "42501"
3662
- ]);
3663
- /**
3664
- * Describe a failed connection attempt in terms a developer can act on.
3665
- *
3666
- * The error a caller catches is Drizzle's wrapper: its message is
3667
- * `Failed query: SELECT 1` and its stack runs through drizzle internals, while
3668
- * the sentence that says what is actually wrong — "password authentication
3669
- * failed for user …", "database … does not exist" — sits in `.cause`. Logging
3670
- * the wrapper, as the bootstrapper used to, tells a developer with a typo in
3671
- * their `DATABASE_URL` nothing at all.
3672
- */
3673
- function classifyConnectFailure(error) {
3674
- const pgError = extractPgError(error);
3675
- const reason = pgError?.message ?? extractCauseMessage(error) ?? (error instanceof Error ? error.message : String(error));
3676
- return {
3677
- fatal: Boolean(pgError?.code && UNRECOVERABLE_CONNECT_CODES.has(pgError.code)),
3678
- reason,
3679
- code: pgError?.code
3680
- };
3681
- }
3682
- /**
3683
- * Detect whether an error is specifically a role-switching permission failure
3684
- * (e.g. "permission denied to set role" or "must be member of role"),
3685
- * as opposed to a table-level permission denial.
3686
- *
3687
- * This is used by the backend driver to auto-disable role switching when the
3688
- * connection user lacks SET ROLE privileges, rather than surfacing a confusing
3689
- * error to the Studio SQL Editor user.
3690
- */
3691
- function isRoleSwitchingPermissionError(error) {
3692
- const pgError = extractPgError(error);
3693
- if (!pgError || pgError.code !== "42501") return false;
3694
- const msg = pgError.message.toLowerCase();
3695
- return msg.includes("set role") || msg.includes("member of role");
3696
- }
3697
- /**
3698
- * Translate a raw PostgreSQL error into a user-friendly message.
3699
- *
3700
- * @param pgError - The extracted PostgreSQL error (from {@link extractPgError})
3701
- * @param context - A human-readable context string (e.g. collection slug or path)
3702
- * @returns An object with a `message` safe for the client and the PG `code`.
3703
- */
3704
- function pgErrorToFriendlyMessage(pgError, context) {
3705
- const detail = pgError.detail;
3706
- const hint = pgError.hint;
3707
- const constraint = pgError.constraint;
3708
- const column = pgError.column;
3709
- const table = pgError.table;
3710
- const dataType = pgError.dataType;
3711
- const pgMessage = pgError.message || "Unknown database error";
3712
- const code = pgError.code || "UNKNOWN";
3713
- const suffix = hint ? ` Hint: ${hint}` : "";
3714
- const tableRef = table ?? context;
3715
- switch (pgError.code) {
3716
- case "23503": return {
3717
- message: detail ? `Foreign key constraint violated: ${detail}${suffix}` : `Cannot complete operation: a foreign key constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
3718
- code
3719
- };
3720
- case "23505": return {
3721
- message: detail ? `Duplicate value: ${detail}${suffix}` : `Cannot complete operation: a unique constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
3722
- code
3723
- };
3724
- case "23502": return {
3725
- message: `Missing required field: "${column ?? "unknown"}" in "${tableRef}" cannot be empty.${suffix}`,
3726
- code
3727
- };
3728
- case "23514": return {
3729
- message: `Validation failed: a check constraint${constraint ? ` (${constraint})` : ""} was violated in "${context}".${suffix}`,
3730
- code
3731
- };
3732
- case "22P02": return {
3733
- message: `Invalid data format in "${context}": ${pgMessage}${suffix}`,
3734
- code
3735
- };
3736
- case "22001": return {
3737
- message: `Value too long for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`,
3738
- code
3739
- };
3740
- case "22003": return {
3741
- message: `Numeric value out of range for column "${column ?? "unknown"}" in "${tableRef}": ${pgMessage}${suffix}`,
3742
- code
3743
- };
3744
- case "42703": return {
3745
- message: `Unknown column in "${tableRef}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,
3746
- code
3747
- };
3748
- case "42P01": return {
3749
- message: `Table not found for "${context}": ${pgMessage}. Check if your schema is up to date (run migrations).${suffix}`,
3750
- code
3751
- };
3752
- case "42501": return {
3753
- message: `Permission denied on "${tableRef}". Check your database credentials and RLS policies.${suffix}`,
3754
- code
3755
- };
3756
- case "28000": return {
3757
- message: `Authorization failed for "${context}". Check your database credentials.${suffix}`,
3758
- code
3759
- };
3760
- default: {
3761
- const parts = [`Database error in "${context}" [${code}]: ${pgMessage}`];
3762
- if (detail) parts.push(`Detail: ${detail}`);
3763
- if (column) parts.push(`Column: ${column}`);
3764
- if (dataType) parts.push(`Data type: ${dataType}`);
3765
- if (constraint) parts.push(`Constraint: ${constraint}`);
3766
- if (hint) parts.push(`Hint: ${hint}`);
3767
- return {
3768
- message: parts.join(". "),
3769
- code
3770
- };
3771
- }
3772
- }
3773
- }
3774
- /**
3775
- * Sanitize any error into a message safe and helpful for the client.
3776
- *
3777
- * A deliberate 4xx (`ApiError`) passes through untouched — the server already
3778
- * decided what the client should read. Otherwise the PG error is extracted
3779
- * from the Drizzle cause chain, falling back to a generic message that
3780
- * doesn't leak SQL.
3781
- *
3782
- * @param error - The raw caught error
3783
- * @param context - A human-readable context string (e.g. collection path)
3784
- * @returns An object with `message` (user-friendly) and optional `code`
3785
- * (the `ApiError` code, or the PG SQLSTATE).
3786
- */
3787
- function sanitizeErrorForClient(error, context) {
3788
- const clientError = asClientFacingError(error);
3789
- if (clientError) {
3790
- const line = `[API ${clientError.statusCode} ${clientError.code ?? "BAD_REQUEST"}] in "${context}": ${clientError.message}`;
3791
- if (clientError.expected) logger.debug(line);
3792
- else logger.warn(`⚠️ ${line}`);
3793
- return {
3794
- message: clientError.message,
3795
- ...clientError.code && { code: clientError.code }
3796
- };
3797
- }
3798
- const pgError = extractPgError(error);
3799
- if (pgError) {
3800
- logger.error(`[PG ${pgError.code}] Error in "${context}"`, {
3801
- code: pgError.code,
3802
- message: pgError.message,
3803
- detail: pgError.detail,
3804
- hint: pgError.hint,
3805
- column: pgError.column,
3806
- table: pgError.table,
3807
- constraint: pgError.constraint,
3808
- dataType: pgError.dataType,
3809
- drizzleMessage: error instanceof Error ? error.message : String(error)
3810
- });
3811
- return pgErrorToFriendlyMessage(pgError, context);
3812
- }
3813
- logger.error(`Database error in "${context}" (no PG error extracted)`, {
3814
- error: error instanceof Error ? error.message : String(error),
3815
- stack: error instanceof Error ? error.stack : void 0,
3816
- cause: error instanceof Error && error.cause ? error.cause instanceof Error ? error.cause.message : String(error.cause) : void 0
3817
- });
3818
- const causeMessage = extractCauseMessage(error);
3819
- if (causeMessage) return { message: `Database error in "${context}": ${causeMessage}` };
3820
- return { message: `Could not load data for "${context}". Check server logs for details.` };
3821
- }
3822
- //#endregion
3823
3868
  //#region src/services/PersistService.ts
3824
3869
  /**
3825
3870
  * Service for handling all row write operations.
@@ -5946,6 +5991,7 @@ var getDrizzleColumn = (propName, prop, collection, collections) => {
5946
5991
  const isId = isIdProperty(propName, prop, collection);
5947
5992
  let baseType = numProp.validation?.integer || isId ? `integer("${colName}")` : `numeric("${colName}")`;
5948
5993
  if (numProp.columnType) if (numProp.columnType === "double precision") baseType = `doublePrecision("${colName}")`;
5994
+ else if (numProp.columnType === "bigint" || numProp.columnType === "bigserial") baseType = `${numProp.columnType}("${colName}", { mode: "number" })`;
5949
5995
  else baseType = `${numProp.columnType}("${colName}")`;
5950
5996
  if ("isId" in numProp && numProp.isId === "increment") columnDefinition = `${baseType}.generatedByDefaultAsIdentity()`;
5951
5997
  else if ("isId" in numProp && typeof numProp.isId === "string" && numProp.isId !== "manual") {
@@ -12225,7 +12271,7 @@ function createPostgresBootstrapper(pgConfig) {
12225
12271
  */
12226
12272
  async ensureCollectionSchema(collections, driverResult, log) {
12227
12273
  const internals = driverResult.internals;
12228
- const { ensureCollectionTables } = await import("./ensure-collection-tables-Da2oGkX2.js");
12274
+ const { ensureCollectionTables } = await import("./ensure-collection-tables-CBQdOETu.js");
12229
12275
  const plan = await ensureCollectionTables({ async query(text) {
12230
12276
  const result = await internals.db.execute(sql.raw(text));
12231
12277
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };
@@ -12250,7 +12296,7 @@ function createPostgresBootstrapper(pgConfig) {
12250
12296
  */
12251
12297
  async ensureCollectionPolicies(collections, driverResult, log) {
12252
12298
  const internals = driverResult.internals;
12253
- const { ensureCollectionPolicies } = await import("./ensure-collection-policies-BrUVgjz3.js");
12299
+ const { ensureCollectionPolicies } = await import("./ensure-collection-policies-ViG8XiPn.js");
12254
12300
  const outcome = await ensureCollectionPolicies({ async query(text) {
12255
12301
  const result = await internals.db.execute(sql.raw(text));
12256
12302
  return { rows: result.rows ?? (Array.isArray(result) ? result : []) };