@objectstack/types 17.0.0 → 17.2.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/CHANGELOG.md +434 -0
- package/dist/index.d.mts +98 -18
- package/dist/index.d.ts +98 -18
- package/dist/index.js +37 -2
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +35 -2
- package/dist/index.mjs.map +1 -1
- package/dist/node.d.mts +79 -9
- package/dist/node.d.ts +79 -9
- package/dist/node.js +15 -8
- package/dist/node.js.map +1 -1
- package/dist/node.mjs +15 -8
- package/dist/node.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -393,7 +393,11 @@ declare const INTERNAL_ERROR_MESSAGE = "Internal server error";
|
|
|
393
393
|
* (a message that *starts* as `SELECT`/`INSERT INTO`/`UPDATE`/`DELETE FROM` —
|
|
394
394
|
* drivers prefix the offending SQL to their message), constraint-violation
|
|
395
395
|
* dumps, which name physical tables and columns, and the
|
|
396
|
-
* {@link DIALECT_LEAK_PHRASINGS}
|
|
396
|
+
* {@link DIALECT_LEAK_PHRASINGS} the list covers — the SQLite family, Postgres
|
|
397
|
+
* and, since #8739, MySQL/MariaDB. A dialect outside that coverage (MSSQL and
|
|
398
|
+
* Oracle are the standing examples) makes this return FALSE without meaning the
|
|
399
|
+
* text is safe; read {@link DIALECT_LEAK_PHRASINGS}' note before sizing
|
|
400
|
+
* anything on a `false`.
|
|
397
401
|
*
|
|
398
402
|
* Does NOT match ordinary business or validation messages, which is why the
|
|
399
403
|
* statement forms are anchored with `startsWith` and the dialect phrasings on
|
|
@@ -724,17 +728,33 @@ declare function sendError(res: EnvelopeResponse, status: number, code: ErrorCod
|
|
|
724
728
|
* unregistered code there is a failing test, not a wire answer.
|
|
725
729
|
*
|
|
726
730
|
* {@link ThrownHttpError.declaredCode} is the producer's own string, verbatim
|
|
727
|
-
* and un-narrowed
|
|
728
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
731
|
-
*
|
|
732
|
-
*
|
|
733
|
-
*
|
|
734
|
-
*
|
|
735
|
-
*
|
|
736
|
-
*
|
|
737
|
-
*
|
|
731
|
+
* and un-narrowed. Until #9106 it was what the dispatcher door put in
|
|
732
|
+
* `error.code`; since the #9106 ruling it is what BOTH doors surface as the
|
|
733
|
+
* wire's `declaredCode` when it is not a vocabulary member (see below).
|
|
734
|
+
*
|
|
735
|
+
* [#8087] The first ruling on that gap (maintainer, 2026-08-12) kept the
|
|
736
|
+
* dispatcher's verbatim spelling and delivered a GATE — the unregistered
|
|
737
|
+
* producers are measured and classified
|
|
738
|
+
* (`packages/runtime/src/dispatcher-error-vocabulary.ts`,
|
|
739
|
+
* `pnpm check:dispatcher-error-vocabulary`) instead of named in prose here.
|
|
740
|
+
* The gate's own first derivation then measured the limb no registration can
|
|
741
|
+
* close: a metadata app's action code crosses the sandbox boundary carrying
|
|
742
|
+
* the app's OWN `.code` (#7867), authored by tenants at runtime.
|
|
743
|
+
*
|
|
744
|
+
* [#9106] That limb was ruled (maintainer, 2026-08-16): **`error.code` is a
|
|
745
|
+
* closed vocabulary at every door.** The dispatcher door now takes
|
|
746
|
+
* {@link ThrownHttpError.code} — the demote this resolver has always computed,
|
|
747
|
+
* and the REST door's spelling since #8016 — and a producer's unregistered
|
|
748
|
+
* string rides the wire's `declaredCode` (declared on `ApiErrorSchema`)
|
|
749
|
+
* instead of `error.code`. #7867's capability is preserved: the author's code
|
|
750
|
+
* still crosses the sandbox and still reaches the wire — in the open,
|
|
751
|
+
* author-authored channel, not the closed one. Use
|
|
752
|
+
* {@link demotedDeclaredCode} to read the spelling a boundary should surface
|
|
753
|
+
* beside the closed `code`.
|
|
754
|
+
*
|
|
755
|
+
* So the doors agree on **status** and on **code** unconditionally now — both
|
|
756
|
+
* answers come from ONE function, which is what keeps agreement a construction
|
|
757
|
+
* rather than two suites agreeing about literals.
|
|
738
758
|
*
|
|
739
759
|
* ## What this deliberately does NOT decide
|
|
740
760
|
*
|
|
@@ -764,10 +784,12 @@ interface ThrownHttpError {
|
|
|
764
784
|
* to `status: 500`, so a caller that must tell "the producer said so" from
|
|
765
785
|
* "I supplied the default" cannot read it off the value. The workaround in
|
|
766
786
|
* the repo was to probe this function with a fallback no producer declares
|
|
767
|
-
* — `resolveThrownHttpError(e, 0).status !== 0
|
|
768
|
-
* `packages/rest`'s publish-classification suite. That is a magic number
|
|
787
|
+
* — `resolveThrownHttpError(e, 0).status !== 0`. That is a magic number
|
|
769
788
|
* standing in for a fact this function already computed, and it fails
|
|
770
|
-
* silently the day a producer declares the sentinel. So the fact is stated
|
|
789
|
+
* silently the day a producer declares the sentinel. So the fact is stated;
|
|
790
|
+
* `packages/rest`'s publish-classification suite now reads
|
|
791
|
+
* `resolveThrownHttpError(error).declaredStatus !== undefined` instead of
|
|
792
|
+
* hand-spelling the workaround.
|
|
771
793
|
*
|
|
772
794
|
* ## Who needs the distinction
|
|
773
795
|
*
|
|
@@ -788,12 +810,39 @@ interface ThrownHttpError {
|
|
|
788
810
|
code: ErrorCode;
|
|
789
811
|
/**
|
|
790
812
|
* The producer's own code, verbatim and un-narrowed, or `undefined` when it
|
|
791
|
-
* declared none.
|
|
792
|
-
*
|
|
813
|
+
* declared none. Never for `error.code` — that slot takes {@link code} at
|
|
814
|
+
* every door (#9106) — but for the wire's `declaredCode` channel when the
|
|
815
|
+
* spelling is not a vocabulary member ({@link demotedDeclaredCode}). See the
|
|
816
|
+
* module note on why there are two.
|
|
793
817
|
*/
|
|
794
818
|
declaredCode?: string;
|
|
795
819
|
/** The thrown message, UNSANITISED — see the module note on disclosure. */
|
|
796
820
|
message: string;
|
|
821
|
+
/**
|
|
822
|
+
* The producer's user-facing refusal text, verbatim — present exactly when
|
|
823
|
+
* the throw carried a non-empty string `userMessage` (#9934).
|
|
824
|
+
*
|
|
825
|
+
* This is the producer-side opt-in the objectui#5210 ruling asked for
|
|
826
|
+
* (maintainer, 2026-08-19, option 1): an application hook's refusal has no
|
|
827
|
+
* way to distinguish author-written user guidance from platform diagnostics,
|
|
828
|
+
* so the console substitutes a generic string on 403 (the recorded #3821
|
|
829
|
+
* fix) and every author-written remedy is suppressed with the diagnostics.
|
|
830
|
+
* A producer that sets `userMessage` on the thrown error is saying, at throw
|
|
831
|
+
* time, "this exact text is addressed to the END USER" — a consumer renders
|
|
832
|
+
* it verbatim and keeps the generic substitution for everything unmarked.
|
|
833
|
+
*
|
|
834
|
+
* Deliberately a FIELD carrying the text, not a boolean beside `message`:
|
|
835
|
+
* the mark and the marked text are one value, so a boundary that rewraps or
|
|
836
|
+
* substitutes `message` (sanitisation, truncation, the sandbox debug
|
|
837
|
+
* wrapper) can never accidentally promote platform prose into the marked
|
|
838
|
+
* channel — the #3821 protection holds by construction. Read through
|
|
839
|
+
* {@link declaredUserMessage}, never with an inline `typeof` probe.
|
|
840
|
+
*
|
|
841
|
+
* Status-agnostic on purpose (the ruling's second constraint): a 400, 403,
|
|
842
|
+
* 409 or 503 refusal may all carry it. It never REPLACES `message` — the
|
|
843
|
+
* diagnostic channel keeps its wording for logs and developers.
|
|
844
|
+
*/
|
|
845
|
+
userMessage?: string;
|
|
797
846
|
/**
|
|
798
847
|
* Structured context: spec-validation `issues[]`, record-validation
|
|
799
848
|
* `fields[]`. Absent rather than `{}` when the throw carried none, so an
|
|
@@ -814,6 +863,7 @@ interface ThrownHttpError {
|
|
|
814
863
|
* | code | `VALIDATION_FAILED` if it is one → a REGISTERED `.code` → derived from the status |
|
|
815
864
|
* | declaredCode | `VALIDATION_FAILED` if it is one → any non-empty string `.code` → absent |
|
|
816
865
|
* | message | `.message` when it is a string → `String(error)` |
|
|
866
|
+
* | userMessage | a non-empty string `.userMessage` → absent (see {@link declaredUserMessage}) |
|
|
817
867
|
*
|
|
818
868
|
* Both status spellings are read because both are produced in this repo:
|
|
819
869
|
* `plugin-approvals`' lifecycle hooks and `metadata-protocol` throw
|
|
@@ -822,6 +872,36 @@ interface ThrownHttpError {
|
|
|
822
872
|
* RECORD_LOCKED` until #7525.
|
|
823
873
|
*/
|
|
824
874
|
declare function resolveThrownHttpError(error: unknown, fallbackStatus?: number): ThrownHttpError;
|
|
875
|
+
/**
|
|
876
|
+
* The user-facing refusal text a thrown error DECLARED, or `undefined` when it
|
|
877
|
+
* declared none (#9934). See {@link ThrownHttpError.userMessage} for what the
|
|
878
|
+
* declaration means and why it is a text-carrying field rather than a flag.
|
|
879
|
+
*
|
|
880
|
+
* The ONE read every boundary applies — the REST classification door, the
|
|
881
|
+
* dispatcher door, and the sandbox side-channel all call this rather than
|
|
882
|
+
* probing `error.userMessage` themselves, so "what counts as marked" cannot
|
|
883
|
+
* fork per door the way the `status`/`statusCode` spelling once did (#7525).
|
|
884
|
+
*
|
|
885
|
+
* A non-string or blank `userMessage` is NOT a declaration: `undefined`, a
|
|
886
|
+
* number, `''` and whitespace-only all answer `undefined`, so nothing invents
|
|
887
|
+
* a marked message for a producer that never wrote one — absent means the
|
|
888
|
+
* consumer keeps its generic substitution (#3821 preserved by construction).
|
|
889
|
+
*/
|
|
890
|
+
declare function declaredUserMessage(error: unknown): string | undefined;
|
|
891
|
+
/**
|
|
892
|
+
* The producer's spelling a boundary should surface as the wire's
|
|
893
|
+
* `declaredCode` beside the closed `code` — or `undefined` when there is
|
|
894
|
+
* nothing to surface (#9106).
|
|
895
|
+
*
|
|
896
|
+
* Present exactly when the throw spelled a code that did NOT survive into
|
|
897
|
+
* {@link ThrownHttpError.code} — i.e. the demote happened. A registered code
|
|
898
|
+
* is already in `code`, so emitting it again would put two spellings of one
|
|
899
|
+
* fact on every refusal; a throw with no code has nothing to declare. Spelled
|
|
900
|
+
* once here rather than as three `!==` comparisons at three exits, so
|
|
901
|
+
* "presence means demotion" (`ApiErrorSchema.declaredCode`'s documented
|
|
902
|
+
* semantics) has one definition.
|
|
903
|
+
*/
|
|
904
|
+
declare function demotedDeclaredCode(thrown: ThrownHttpError): string | undefined;
|
|
825
905
|
|
|
826
906
|
/** The HTTP status a validation failure maps to when the error names none. */
|
|
827
907
|
declare const VALIDATION_FAILED_STATUS = 400;
|
|
@@ -1327,4 +1407,4 @@ interface RuntimePlugin {
|
|
|
1327
1407
|
onStart?: (ctx: RuntimeContext) => void | Promise<void>;
|
|
1328
1408
|
}
|
|
1329
1409
|
|
|
1330
|
-
export { type EnvelopeResponse, GLOBAL_UNIQUE_CONFIRMATION_REQUIRED, GLOBAL_UNIQUE_ISOLATED_PRESCRIPTION, type GlobalUniqueAttestation, type GlobalUniqueFinding, type IKernel, INTERNAL_ERROR_MESSAGE, type KeysetPageQuery, type KeysetWalk, type KeysetWalkOptions, type RuntimeContext, type RuntimePlugin, type ThrownHttpError, VALIDATION_FAILED_STATUS, type ValidationFailureDetails, _resetEnvDeprecationWarnings, buildGlobalUniqueStopMessage, collectConfiguredLocales, collectGlobalUniques, declaredIndexUniqueIsGlobal, declaresServerFault, describeGlobalUniqueFinding, emitDegradedBootBanner, fieldUniqueIsGlobal, fieldsFromZodIssues, globalUniqueFindingId, isMcpServerEnabled, isModuleNotFoundError, isPlatformOwnedObject, isRelationSubObjectPhrase, isUnbackedConflictTargetError, isUniqueViolationError, keysetWalk, looksLikeInternalErrorLeak, matchMissingColumnOfRelation, postureGatesGlobalUniques, readEnvWithDeprecation, recordGlobalUniqueAttestation, resolveAllowDegradedTenancy, resolveAllowDevPlugin, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture, resolveThrownHttpError, sendError, sendOk, stampSearchPinyinEnabled, unconfirmedGlobalUniques, uniqueViolationColumn, validationFailure, validationFailureDetails };
|
|
1410
|
+
export { type EnvelopeResponse, GLOBAL_UNIQUE_CONFIRMATION_REQUIRED, GLOBAL_UNIQUE_ISOLATED_PRESCRIPTION, type GlobalUniqueAttestation, type GlobalUniqueFinding, type IKernel, INTERNAL_ERROR_MESSAGE, type KeysetPageQuery, type KeysetWalk, type KeysetWalkOptions, type RuntimeContext, type RuntimePlugin, type ThrownHttpError, VALIDATION_FAILED_STATUS, type ValidationFailureDetails, _resetEnvDeprecationWarnings, buildGlobalUniqueStopMessage, collectConfiguredLocales, collectGlobalUniques, declaredIndexUniqueIsGlobal, declaredUserMessage, declaresServerFault, demotedDeclaredCode, describeGlobalUniqueFinding, emitDegradedBootBanner, fieldUniqueIsGlobal, fieldsFromZodIssues, globalUniqueFindingId, isMcpServerEnabled, isModuleNotFoundError, isPlatformOwnedObject, isRelationSubObjectPhrase, isUnbackedConflictTargetError, isUniqueViolationError, keysetWalk, looksLikeInternalErrorLeak, matchMissingColumnOfRelation, postureGatesGlobalUniques, readEnvWithDeprecation, recordGlobalUniqueAttestation, resolveAllowDegradedTenancy, resolveAllowDevPlugin, resolveAllowDriverConnectFailure, resolveMcpStdioAutoStart, resolveMultiOrgEnabled, resolveOrgLimit, resolveSandboxTimeoutMs, resolveSearchPinyinEnabled, resolveTenancyPosture, resolveThrownHttpError, sendError, sendOk, stampSearchPinyinEnabled, unconfirmedGlobalUniques, uniqueViolationColumn, validationFailure, validationFailureDetails };
|
package/dist/index.js
CHANGED
|
@@ -29,7 +29,9 @@ __export(index_exports, {
|
|
|
29
29
|
collectConfiguredLocales: () => collectConfiguredLocales,
|
|
30
30
|
collectGlobalUniques: () => collectGlobalUniques,
|
|
31
31
|
declaredIndexUniqueIsGlobal: () => declaredIndexUniqueIsGlobal,
|
|
32
|
+
declaredUserMessage: () => declaredUserMessage,
|
|
32
33
|
declaresServerFault: () => declaresServerFault,
|
|
34
|
+
demotedDeclaredCode: () => demotedDeclaredCode,
|
|
33
35
|
describeGlobalUniqueFinding: () => describeGlobalUniqueFinding,
|
|
34
36
|
emitDegradedBootBanner: () => emitDegradedBootBanner,
|
|
35
37
|
fieldUniqueIsGlobal: () => fieldUniqueIsGlobal,
|
|
@@ -215,7 +217,29 @@ var DIALECT_LEAK_PHRASINGS = [
|
|
|
215
217
|
// SQLite/libsql, message-only form. The `sqlite_` limb below catches these
|
|
216
218
|
// only when the driver prefixed its code; `better-sqlite3` and libsql both
|
|
217
219
|
// raise them bare, which is the shape measured across this repo.
|
|
218
|
-
/\bno such (?:table|column):/i
|
|
220
|
+
/\bno such (?:table|column):/i,
|
|
221
|
+
// [#8739] MySQL/MariaDB ER_NO_SUCH_TABLE (1146): `Table 'app.t' doesn't
|
|
222
|
+
// exist`. Its own template, not a spelling of the Postgres one — MySQL
|
|
223
|
+
// contracts the verb and quotes `db.table` as a single identifier — so the
|
|
224
|
+
// `relation|column … does not exist` limb above cannot reach it. The quotes
|
|
225
|
+
// are required for the same reason they are there: the driver always emits
|
|
226
|
+
// them and prose about a table usually does not.
|
|
227
|
+
/\btable\s+["'`][^"'`]+["'`]\s+doesn't exist/i,
|
|
228
|
+
// [#8739] MySQL/MariaDB ER_BAD_FIELD_ERROR (1054): `Unknown column 'c' in
|
|
229
|
+
// 'field list'`. BOTH quoted parts are required. The second is MySQL's clause
|
|
230
|
+
// name — `field list`, `where clause`, `order clause`, `on clause` — and it
|
|
231
|
+
// is the half that makes this the driver's template rather than a sentence
|
|
232
|
+
// that merely calls a column unknown, which an import or mapping feature has
|
|
233
|
+
// every right to say.
|
|
234
|
+
/\bunknown column\s+["'`][^"'`]+["'`]\s+in\s+["'`][^"'`]+["'`]/i,
|
|
235
|
+
// [#8739] MySQL/MariaDB ER_DUP_ENTRY (1062): `Duplicate entry
|
|
236
|
+
// 'acme@example.com' for key 'crm_account.email'`. `for key` + a quoted index
|
|
237
|
+
// is the anchor; the VALUE half is matched loosely and lazily because it is
|
|
238
|
+
// the caller's own text and MySQL does not escape a quote inside it
|
|
239
|
+
// (`Duplicate entry 'O'Brien' for key 'i'` is a real shape). A bare
|
|
240
|
+
// `duplicate entry` with no `for key '…'` tail is not this template and is
|
|
241
|
+
// left alone.
|
|
242
|
+
/\bduplicate entry\s+["'`].*?["'`]\s+for key\s+["'`][^"'`]+["'`]/i
|
|
219
243
|
];
|
|
220
244
|
function looksLikeInternalErrorLeak(message) {
|
|
221
245
|
if (!message) return false;
|
|
@@ -351,15 +375,24 @@ function resolveThrownHttpError(error, fallbackStatus = 500) {
|
|
|
351
375
|
...issues ? { issues } : {},
|
|
352
376
|
...validation ? { fields: validation.fields } : {}
|
|
353
377
|
};
|
|
378
|
+
const userMessage = declaredUserMessage(error);
|
|
354
379
|
return {
|
|
355
380
|
status,
|
|
356
381
|
...declaredStatus !== void 0 ? { declaredStatus } : {},
|
|
357
382
|
code,
|
|
358
383
|
...declaredCode !== void 0 ? { declaredCode } : {},
|
|
359
384
|
message: typeof e?.message === "string" ? e.message : String(error),
|
|
385
|
+
...userMessage !== void 0 ? { userMessage } : {},
|
|
360
386
|
...Object.keys(details).length > 0 ? { details } : {}
|
|
361
387
|
};
|
|
362
388
|
}
|
|
389
|
+
function declaredUserMessage(error) {
|
|
390
|
+
const declared = error?.userMessage;
|
|
391
|
+
return typeof declared === "string" && declared.trim().length > 0 ? declared : void 0;
|
|
392
|
+
}
|
|
393
|
+
function demotedDeclaredCode(thrown) {
|
|
394
|
+
return thrown.declaredCode !== void 0 && thrown.declaredCode !== thrown.code ? thrown.declaredCode : void 0;
|
|
395
|
+
}
|
|
363
396
|
|
|
364
397
|
// src/relation-sub-object.ts
|
|
365
398
|
function matchMissingColumnOfRelation(message) {
|
|
@@ -375,7 +408,7 @@ var RELATION_SUB_OBJECT = /["'`][^"'`]+["'`]\s+of relation\s/i;
|
|
|
375
408
|
var UNIQUE_VIOLATION = {
|
|
376
409
|
codes: /* @__PURE__ */ new Set(["23505", "ER_DUP_ENTRY", "SQLITE_CONSTRAINT_UNIQUE"]),
|
|
377
410
|
errnos: /* @__PURE__ */ new Set([1062]),
|
|
378
|
-
message: /unique constraint|unique violation|duplicate key|duplicate entry/i
|
|
411
|
+
message: /unique constraint failed|violates unique constraint|unique violation|duplicate key|duplicate entry/i
|
|
379
412
|
};
|
|
380
413
|
var MAX_CAUSE_DEPTH = 4;
|
|
381
414
|
function isUniqueViolationError(error) {
|
|
@@ -562,7 +595,9 @@ function postureGatesGlobalUniques(posture) {
|
|
|
562
595
|
collectConfiguredLocales,
|
|
563
596
|
collectGlobalUniques,
|
|
564
597
|
declaredIndexUniqueIsGlobal,
|
|
598
|
+
declaredUserMessage,
|
|
565
599
|
declaresServerFault,
|
|
600
|
+
demotedDeclaredCode,
|
|
566
601
|
describeGlobalUniqueFinding,
|
|
567
602
|
emitDegradedBootBanner,
|
|
568
603
|
fieldUniqueIsGlobal,
|