@objectstack/types 17.0.0-rc.6 → 17.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.mjs CHANGED
@@ -136,10 +136,47 @@ function _resetEnvDeprecationWarnings() {
136
136
 
137
137
  // src/error-leak.ts
138
138
  var INTERNAL_ERROR_MESSAGE = "Internal server error";
139
+ var DIALECT_LEAK_PHRASINGS = [
140
+ // Postgres 42P01 / 42703 (and, as a superstring, the `… of relation "…"`
141
+ // sub-object family: 42704 and friends). The quotes are required because
142
+ // Postgres always emits them here.
143
+ /\b(?:relation|column)\s+["'`][^"'`]+["'`]\s+does not exist/i,
144
+ // Postgres 42501. Restricted to physical object kinds: `schema`, `view`,
145
+ // `function` and `column` are all ObjectStack AUTHORING vocabulary, so a
146
+ // product message could legitimately use them and a miss is the cheap
147
+ // direction (the outcome is already a 5xx).
148
+ /\bpermission denied for (?:table|relation|sequence|database)\b/i,
149
+ // SQLite/libsql, message-only form. The `sqlite_` limb below catches these
150
+ // only when the driver prefixed its code; `better-sqlite3` and libsql both
151
+ // raise them bare, which is the shape measured across this repo.
152
+ /\bno such (?:table|column):/i,
153
+ // [#8739] MySQL/MariaDB ER_NO_SUCH_TABLE (1146): `Table 'app.t' doesn't
154
+ // exist`. Its own template, not a spelling of the Postgres one — MySQL
155
+ // contracts the verb and quotes `db.table` as a single identifier — so the
156
+ // `relation|column … does not exist` limb above cannot reach it. The quotes
157
+ // are required for the same reason they are there: the driver always emits
158
+ // them and prose about a table usually does not.
159
+ /\btable\s+["'`][^"'`]+["'`]\s+doesn't exist/i,
160
+ // [#8739] MySQL/MariaDB ER_BAD_FIELD_ERROR (1054): `Unknown column 'c' in
161
+ // 'field list'`. BOTH quoted parts are required. The second is MySQL's clause
162
+ // name — `field list`, `where clause`, `order clause`, `on clause` — and it
163
+ // is the half that makes this the driver's template rather than a sentence
164
+ // that merely calls a column unknown, which an import or mapping feature has
165
+ // every right to say.
166
+ /\bunknown column\s+["'`][^"'`]+["'`]\s+in\s+["'`][^"'`]+["'`]/i,
167
+ // [#8739] MySQL/MariaDB ER_DUP_ENTRY (1062): `Duplicate entry
168
+ // 'acme@example.com' for key 'crm_account.email'`. `for key` + a quoted index
169
+ // is the anchor; the VALUE half is matched loosely and lazily because it is
170
+ // the caller's own text and MySQL does not escape a quote inside it
171
+ // (`Duplicate entry 'O'Brien' for key 'i'` is a real shape). A bare
172
+ // `duplicate entry` with no `for key '…'` tail is not this template and is
173
+ // left alone.
174
+ /\bduplicate entry\s+["'`].*?["'`]\s+for key\s+["'`][^"'`]+["'`]/i
175
+ ];
139
176
  function looksLikeInternalErrorLeak(message) {
140
177
  if (!message) return false;
141
178
  const lower = String(message).toLowerCase();
142
- return lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key");
179
+ return lower.includes("sqlite_") || lower.includes("sqlstate") || lower.startsWith("insert into ") || lower.startsWith("update ") || lower.startsWith("select ") || lower.startsWith("delete from ") || lower.includes("constraint failed") || lower.includes("unique constraint") || lower.includes("foreign key") || DIALECT_LEAK_PHRASINGS.some((pattern) => pattern.test(lower));
143
180
  }
144
181
  function declaresServerFault(err) {
145
182
  if (typeof err !== "object" || err === null) return false;
@@ -224,6 +261,71 @@ function sendError(res, status, code, message, extra) {
224
261
  res.status(status).json({ success: false, error: { code, message, ...extra } });
225
262
  }
226
263
 
264
+ // src/thrown-http-error.ts
265
+ import { ErrorCode, standardErrorCodeForHttpStatus } from "@objectstack/spec/api";
266
+
267
+ // src/validation-failure.ts
268
+ import { zodIssuesToFields } from "@objectstack/spec/api";
269
+ var VALIDATION_FAILED_STATUS = 400;
270
+ function validationFailureDetails(err) {
271
+ if (!err) return void 0;
272
+ if (err.code !== "VALIDATION_FAILED" && err.name !== "ValidationError") return void 0;
273
+ return {
274
+ code: "VALIDATION_FAILED",
275
+ fields: Array.isArray(err.fields) ? err.fields : []
276
+ };
277
+ }
278
+ function validationFailure(message, fields) {
279
+ const err = new Error(message);
280
+ err.name = "ValidationError";
281
+ err.code = "VALIDATION_FAILED";
282
+ err.fields = fields;
283
+ return err;
284
+ }
285
+ function fieldsFromZodIssues(issues, ...input) {
286
+ return zodIssuesToFields(issues, ...input).map(
287
+ (entry) => entry.field === "" ? { ...entry, field: "(body)" } : entry
288
+ );
289
+ }
290
+
291
+ // src/thrown-http-error.ts
292
+ function resolveThrownHttpError(error, fallbackStatus = 500) {
293
+ const e = error;
294
+ const validation = validationFailureDetails(e);
295
+ const declaredStatus = typeof e?.status === "number" ? e.status : typeof e?.statusCode === "number" ? e.statusCode : validation ? VALIDATION_FAILED_STATUS : void 0;
296
+ const status = declaredStatus ?? fallbackStatus;
297
+ const spelled = typeof e?.code === "string" && e.code !== "" ? e.code : void 0;
298
+ const registered = spelled !== void 0 && ErrorCode.safeParse(spelled).success ? spelled : void 0;
299
+ const code = validation ? validation.code : registered ?? standardErrorCodeForHttpStatus(status);
300
+ const declaredCode = validation ? validation.code : spelled;
301
+ const issues = Array.isArray(e?.issues) ? e.issues : void 0;
302
+ const details = {
303
+ // A truthy NON-string `code` (a driver errno, say) is context and stays
304
+ // context — promoting it would put a number in the field callers branch on,
305
+ // which is the drift #3842 removed.
306
+ ...!validation && e?.code && typeof e.code !== "string" ? { code: e.code } : {},
307
+ ...issues ? { issues } : {},
308
+ ...validation ? { fields: validation.fields } : {}
309
+ };
310
+ const userMessage = declaredUserMessage(error);
311
+ return {
312
+ status,
313
+ ...declaredStatus !== void 0 ? { declaredStatus } : {},
314
+ code,
315
+ ...declaredCode !== void 0 ? { declaredCode } : {},
316
+ message: typeof e?.message === "string" ? e.message : String(error),
317
+ ...userMessage !== void 0 ? { userMessage } : {},
318
+ ...Object.keys(details).length > 0 ? { details } : {}
319
+ };
320
+ }
321
+ function declaredUserMessage(error) {
322
+ const declared = error?.userMessage;
323
+ return typeof declared === "string" && declared.trim().length > 0 ? declared : void 0;
324
+ }
325
+ function demotedDeclaredCode(thrown) {
326
+ return thrown.declaredCode !== void 0 && thrown.declaredCode !== thrown.code ? thrown.declaredCode : void 0;
327
+ }
328
+
227
329
  // src/relation-sub-object.ts
228
330
  function matchMissingColumnOfRelation(message) {
229
331
  return MISSING_COLUMN_OF_RELATION.exec(message)?.[1];
@@ -238,7 +340,7 @@ var RELATION_SUB_OBJECT = /["'`][^"'`]+["'`]\s+of relation\s/i;
238
340
  var UNIQUE_VIOLATION = {
239
341
  codes: /* @__PURE__ */ new Set(["23505", "ER_DUP_ENTRY", "SQLITE_CONSTRAINT_UNIQUE"]),
240
342
  errnos: /* @__PURE__ */ new Set([1062]),
241
- message: /unique constraint|unique violation|duplicate key|duplicate entry/i
343
+ message: /unique constraint failed|violates unique constraint|unique violation|duplicate key|duplicate entry/i
242
344
  };
243
345
  var MAX_CAUSE_DEPTH = 4;
244
346
  function isUniqueViolationError(error) {
@@ -300,6 +402,23 @@ function uniqueViolationColumn(error) {
300
402
  return findUniqueViolationColumn(error, 0);
301
403
  }
302
404
 
405
+ // src/unbacked-conflict-target.ts
406
+ var UNBACKED_CONFLICT_TARGET = {
407
+ message: /ON CONFLICT clause does not match any PRIMARY KEY or UNIQUE constraint|there is no unique or exclusion constraint matching the ON CONFLICT specification/i
408
+ };
409
+ var MAX_CAUSE_DEPTH2 = 4;
410
+ function isUnbackedConflictTargetError(error) {
411
+ return matchesUnbackedConflictTarget(error, 0);
412
+ }
413
+ function matchesUnbackedConflictTarget(error, depth) {
414
+ if (error === null || error === void 0 || depth > MAX_CAUSE_DEPTH2) return false;
415
+ if (typeof error === "string") return UNBACKED_CONFLICT_TARGET.message.test(error);
416
+ if (typeof error !== "object") return false;
417
+ const err = error;
418
+ if (typeof err.message === "string" && UNBACKED_CONFLICT_TARGET.message.test(err.message)) return true;
419
+ return matchesUnbackedConflictTarget(err.cause, depth + 1);
420
+ }
421
+
303
422
  // src/unique-scope-install-gate.ts
304
423
  import { normalizeTenancyPosture as normalizeTenancyPosture2 } from "@objectstack/spec/security";
305
424
  var SYS_OBJECT_PREFIXES = ["sys_", "base_"];
@@ -401,20 +520,25 @@ export {
401
520
  GLOBAL_UNIQUE_CONFIRMATION_REQUIRED,
402
521
  GLOBAL_UNIQUE_ISOLATED_PRESCRIPTION,
403
522
  INTERNAL_ERROR_MESSAGE,
523
+ VALIDATION_FAILED_STATUS,
404
524
  _resetEnvDeprecationWarnings,
405
525
  buildGlobalUniqueStopMessage,
406
526
  collectConfiguredLocales,
407
527
  collectGlobalUniques,
408
528
  declaredIndexUniqueIsGlobal,
529
+ declaredUserMessage,
409
530
  declaresServerFault,
531
+ demotedDeclaredCode,
410
532
  describeGlobalUniqueFinding,
411
533
  emitDegradedBootBanner,
412
534
  fieldUniqueIsGlobal,
535
+ fieldsFromZodIssues,
413
536
  globalUniqueFindingId,
414
537
  isMcpServerEnabled,
415
538
  isModuleNotFoundError,
416
539
  isPlatformOwnedObject,
417
540
  isRelationSubObjectPhrase,
541
+ isUnbackedConflictTargetError,
418
542
  isUniqueViolationError,
419
543
  keysetWalk,
420
544
  looksLikeInternalErrorLeak,
@@ -431,10 +555,13 @@ export {
431
555
  resolveSandboxTimeoutMs,
432
556
  resolveSearchPinyinEnabled,
433
557
  resolveTenancyPosture,
558
+ resolveThrownHttpError,
434
559
  sendError,
435
560
  sendOk,
436
561
  stampSearchPinyinEnabled,
437
562
  unconfirmedGlobalUniques,
438
- uniqueViolationColumn
563
+ uniqueViolationColumn,
564
+ validationFailure,
565
+ validationFailureDetails
439
566
  };
440
567
  //# sourceMappingURL=index.mjs.map