turbine-orm 0.61.0 → 0.62.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/README.md +65 -21
- package/dist/cjs/cli/config.d.ts +40 -0
- package/dist/cjs/cli/config.js +74 -2
- package/dist/cjs/cli/index.d.ts +85 -1
- package/dist/cjs/cli/index.js +323 -24
- package/dist/cjs/cli/mcp.d.ts +8 -0
- package/dist/cjs/cli/mcp.js +448 -29
- package/dist/cjs/cli/pii-tags.d.ts +64 -9
- package/dist/cjs/cli/pii-tags.js +218 -39
- package/dist/cjs/cli/studio-ui.generated.js +1 -1
- package/dist/cjs/cli/studio.d.ts +23 -0
- package/dist/cjs/cli/studio.js +126 -53
- package/dist/cjs/cli/ui.d.ts +15 -1
- package/dist/cjs/cli/ui.js +19 -5
- package/dist/cjs/client.js +186 -3
- package/dist/cjs/errors.d.ts +38 -1
- package/dist/cjs/errors.js +235 -24
- package/dist/cjs/index.d.ts +2 -2
- package/dist/cjs/index.js +7 -2
- package/dist/cjs/pipeline.js +15 -2
- package/dist/cjs/powql.d.ts +12 -0
- package/dist/cjs/powql.js +46 -21
- package/dist/cjs/prisma-compat.d.ts +15 -5
- package/dist/cjs/prisma-compat.js +273 -78
- package/dist/cjs/query/aggregates.d.ts +1 -1
- package/dist/cjs/query/aggregates.js +24 -10
- package/dist/cjs/query/batched-loader.d.ts +9 -4
- package/dist/cjs/query/batched-loader.js +4 -1
- package/dist/cjs/query/builder.d.ts +47 -0
- package/dist/cjs/query/builder.js +125 -21
- package/dist/cjs/query/index.d.ts +3 -1
- package/dist/cjs/query/index.js +7 -1
- package/dist/cjs/query/option-surface.d.ts +11 -0
- package/dist/cjs/query/option-surface.js +13 -0
- package/dist/cjs/query/relations.d.ts +8 -0
- package/dist/cjs/query/relations.js +21 -1
- package/dist/cjs/query/types.d.ts +152 -18
- package/dist/cjs/query/types.js +212 -1
- package/dist/cjs/query/where.d.ts +3 -3
- package/dist/cjs/query/where.js +8 -2
- package/dist/cjs/query/writes.js +10 -9
- package/dist/cli/config.d.ts +40 -0
- package/dist/cli/config.js +73 -2
- package/dist/cli/index.d.ts +85 -1
- package/dist/cli/index.js +321 -26
- package/dist/cli/mcp.d.ts +8 -0
- package/dist/cli/mcp.js +448 -29
- package/dist/cli/pii-tags.d.ts +64 -9
- package/dist/cli/pii-tags.js +217 -39
- package/dist/cli/studio-ui.generated.js +1 -1
- package/dist/cli/studio.d.ts +23 -0
- package/dist/cli/studio.js +125 -53
- package/dist/cli/ui.d.ts +15 -1
- package/dist/cli/ui.js +18 -4
- package/dist/client.js +187 -4
- package/dist/errors.d.ts +38 -1
- package/dist/errors.js +234 -23
- package/dist/index.d.ts +2 -2
- package/dist/index.js +5 -2
- package/dist/pipeline.js +15 -2
- package/dist/powql.d.ts +12 -0
- package/dist/powql.js +46 -21
- package/dist/prisma-compat.d.ts +15 -5
- package/dist/prisma-compat.js +274 -79
- package/dist/query/aggregates.d.ts +1 -1
- package/dist/query/aggregates.js +24 -10
- package/dist/query/batched-loader.d.ts +9 -4
- package/dist/query/batched-loader.js +4 -1
- package/dist/query/builder.d.ts +47 -0
- package/dist/query/builder.js +124 -21
- package/dist/query/index.d.ts +3 -1
- package/dist/query/index.js +2 -0
- package/dist/query/option-surface.d.ts +11 -0
- package/dist/query/option-surface.js +13 -0
- package/dist/query/relations.d.ts +8 -0
- package/dist/query/relations.js +21 -1
- package/dist/query/types.d.ts +152 -18
- package/dist/query/types.js +207 -2
- package/dist/query/where.d.ts +3 -3
- package/dist/query/where.js +8 -2
- package/dist/query/writes.js +10 -9
- package/package.json +13 -3
package/dist/cjs/errors.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* All Turbine errors extend TurbineError which includes a `code` property.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.ReadOnlyError = exports.UnsupportedFeatureError = exports.OptimisticLockError = exports.PipelineError = exports.ExclusionConstraintError = exports.CheckConstraintError = exports.SerializationFailureError = exports.DeadlockError = exports.NotNullViolationError = exports.ForeignKeyError = exports.UniqueConstraintError = exports.CircularRelationError = exports.MigrationError = exports.RelationError = exports.ConnectionError = exports.ValidationError = exports.TimeoutError = exports.NotFoundError = exports.TurbineError = exports.TurbineErrorCode = void 0;
|
|
9
|
+
exports.ReadOnlyError = exports.UnsupportedFeatureError = exports.OptimisticLockError = exports.PipelineError = exports.ExclusionConstraintError = exports.CheckConstraintError = exports.SerializationFailureError = exports.DeadlockError = exports.NotNullViolationError = exports.ForeignKeyError = exports.UniqueConstraintError = exports.CircularRelationError = exports.MigrationError = exports.RelationError = exports.ConnectionError = exports.ValidationError = exports.TimeoutError = exports.NotFoundError = exports.REDACTED_DETAIL = exports.TurbineError = exports.TurbineErrorCode = void 0;
|
|
10
10
|
exports.setErrorMessageMode = setErrorMessageMode;
|
|
11
11
|
exports.getErrorMessageMode = getErrorMessageMode;
|
|
12
12
|
exports.describeTargetForMessage = describeTargetForMessage;
|
|
@@ -50,7 +50,13 @@ function formatErrorMessage(code, message) {
|
|
|
50
50
|
class TurbineError extends Error {
|
|
51
51
|
code;
|
|
52
52
|
constructor(code, message, options) {
|
|
53
|
-
|
|
53
|
+
// The cause is redacted in 'safe' mode (see redactCauseForMode). Only pass
|
|
54
|
+
// an options object through when the caller actually supplied a `cause`
|
|
55
|
+
// key: `new Error(msg, {})` defines no `cause` own property, while
|
|
56
|
+
// `new Error(msg, { cause: undefined })` defines one whose value is
|
|
57
|
+
// undefined, and error-serializing sinks tell those two apart.
|
|
58
|
+
const opts = options && 'cause' in options ? { ...options, cause: redactCauseForMode(options.cause) } : options;
|
|
59
|
+
super(formatErrorMessage(code, message), opts);
|
|
54
60
|
this.name = 'TurbineError';
|
|
55
61
|
this.code = code;
|
|
56
62
|
}
|
|
@@ -65,6 +71,19 @@ let errorMessageMode = 'safe';
|
|
|
65
71
|
* clause (e.g. `where: { id, email }`). Values are redacted.
|
|
66
72
|
* - `'verbose'`: the message includes the full JSON-serialized where
|
|
67
73
|
* clause (e.g. `where: {"id":1,"email":"alice@x.com"}`).
|
|
74
|
+
*
|
|
75
|
+
* SCOPE, stated precisely because the useful version of this contract is the
|
|
76
|
+
* one that is true. 'safe' mode redacts row values from the surfaces Turbine
|
|
77
|
+
* OWNS: its own error messages, and the `detail` field of a driver error it
|
|
78
|
+
* wraps and attaches as `.cause` (see redactCauseForMode).
|
|
79
|
+
*
|
|
80
|
+
* It is NOT a blanket guarantee that no row value can be reached from a thrown
|
|
81
|
+
* error. A driver error whose SQLSTATE {@link wrapPgError} does not classify is
|
|
82
|
+
* returned UNCHANGED, and some of those carry a value in the `message` field
|
|
83
|
+
* itself, where nothing can be removed without destroying the diagnosis:
|
|
84
|
+
* `22P02 invalid input syntax for type integer: "alice@example.com"` is the
|
|
85
|
+
* common one. Treat 'safe' mode as removing Turbine's own contribution to the
|
|
86
|
+
* leak, not as a log-scrubbing boundary.
|
|
68
87
|
*/
|
|
69
88
|
function setErrorMessageMode(mode) {
|
|
70
89
|
errorMessageMode = mode;
|
|
@@ -73,6 +92,107 @@ function setErrorMessageMode(mode) {
|
|
|
73
92
|
function getErrorMessageMode() {
|
|
74
93
|
return errorMessageMode;
|
|
75
94
|
}
|
|
95
|
+
/**
|
|
96
|
+
* The marker left where a driver `detail` string was removed in 'safe' mode.
|
|
97
|
+
* Re-exported from the package root so tests and callers writing log
|
|
98
|
+
* assertions can match on it without hardcoding the wording.
|
|
99
|
+
*/
|
|
100
|
+
exports.REDACTED_DETAIL = '[redacted by turbine errorMessages:"safe"]';
|
|
101
|
+
/**
|
|
102
|
+
* Postgres puts the CONFLICTING ROW VALUES in the `detail` field of a
|
|
103
|
+
* constraint error, and nowhere else: `Key (email)=(alice@example.com) already
|
|
104
|
+
* exists.` for 23505, `Failing row contains (7, alice@example.com, …)` for
|
|
105
|
+
* 23502. The `message` field carries only relation/constraint/column NAMES.
|
|
106
|
+
*
|
|
107
|
+
* 'safe' mode keeps those values out of the Turbine error's own message, but
|
|
108
|
+
* the raw driver error used to be attached verbatim as `.cause`, so the values
|
|
109
|
+
* still reached every place an error object gets rendered whole:
|
|
110
|
+
* - `console.error(err)` / an uncaught rejection: Node's error printer walks
|
|
111
|
+
* the cause chain and prints `[cause]: … detail: 'Key (email)=(…)'`. Note
|
|
112
|
+
* that `cause` is ALREADY non-enumerable (the Error constructor defines it
|
|
113
|
+
* that way) and Node prints it anyway, so hiding the property is not a fix;
|
|
114
|
+
* - Sentry and similar sinks link `cause` chains by default and serialize
|
|
115
|
+
* each link's own properties.
|
|
116
|
+
*
|
|
117
|
+
* So in 'safe' mode the cause is replaced by a shallow clone with `detail`
|
|
118
|
+
* swapped for {@link REDACTED_DETAIL}. Cloning rather than mutating leaves the
|
|
119
|
+
* driver's own object untouched (a caller holding it from their own catch sees
|
|
120
|
+
* what the driver produced).
|
|
121
|
+
*
|
|
122
|
+
* The clone must remain a REAL error, which is the part that is easy to get
|
|
123
|
+
* wrong. `Object.create(proto, descriptors)` looks equivalent and is not: V8
|
|
124
|
+
* installs `stack` as an own ACCESSOR whose backing store is the internal
|
|
125
|
+
* [[ErrorData]] slot, and that slot is not a property, so it is not copied. The
|
|
126
|
+
* result reads `cause.stack === undefined`, `util.types.isNativeError(cause) ===
|
|
127
|
+
* false` and `Object.prototype.toString.call(cause) === '[object Object]'`, i.e.
|
|
128
|
+
* every log serializer that does `err.cause.stack.split('\n')` throws a
|
|
129
|
+
* TypeError and Sentry/pino drop the cause's frames. So the clone starts life
|
|
130
|
+
* as `new Error()` (which HAS the slot), is re-prototyped to the driver error's
|
|
131
|
+
* own prototype, and takes the original's stack as a plain string. That keeps
|
|
132
|
+
* `cause instanceof pg.DatabaseError`, `cause.code === '23505'`, the native
|
|
133
|
+
* brand, and the frames.
|
|
134
|
+
*
|
|
135
|
+
* In 'verbose' mode the cause passes through untouched: that mode's documented
|
|
136
|
+
* job is full-fidelity debugging.
|
|
137
|
+
*/
|
|
138
|
+
function redactCauseForMode(cause) {
|
|
139
|
+
if (errorMessageMode === 'verbose')
|
|
140
|
+
return cause;
|
|
141
|
+
if (!cause || typeof cause !== 'object')
|
|
142
|
+
return cause;
|
|
143
|
+
const detail = cause.detail;
|
|
144
|
+
// Nothing value-bearing to remove: return the original object so the common
|
|
145
|
+
// case (a non-pg cause, or a pg error without a detail) allocates nothing and
|
|
146
|
+
// keeps object identity with what the driver threw.
|
|
147
|
+
if (typeof detail !== 'string' || detail.length === 0)
|
|
148
|
+
return cause;
|
|
149
|
+
try {
|
|
150
|
+
const descriptors = Object.getOwnPropertyDescriptors(cause);
|
|
151
|
+
// Replace the descriptor rather than assigning after the clone exists: a
|
|
152
|
+
// non-writable `detail` would make the assignment throw in strict mode
|
|
153
|
+
// (every module here is ESM, so it always would), and losing the cause is
|
|
154
|
+
// worse than paying for one descriptor literal.
|
|
155
|
+
descriptors.detail = {
|
|
156
|
+
value: exports.REDACTED_DETAIL,
|
|
157
|
+
writable: true,
|
|
158
|
+
enumerable: descriptors.detail?.enumerable ?? true,
|
|
159
|
+
configurable: true,
|
|
160
|
+
};
|
|
161
|
+
// Brand check rather than `instanceof Error`, so a driver error thrown from
|
|
162
|
+
// another realm (a worker, a bundled duplicate of pg) is still recognized.
|
|
163
|
+
const isError = Object.prototype.toString.call(cause) === '[object Error]';
|
|
164
|
+
if (!isError)
|
|
165
|
+
return Object.create(Object.getPrototypeOf(cause), descriptors);
|
|
166
|
+
// `new Error()` is the only way to obtain the [[ErrorData]] slot; the
|
|
167
|
+
// prototype is then pointed at the driver error's, so `instanceof` and
|
|
168
|
+
// `.name` behave exactly as before.
|
|
169
|
+
const clone = new Error();
|
|
170
|
+
Object.setPrototypeOf(clone, Object.getPrototypeOf(cause));
|
|
171
|
+
// The clone's own fresh `stack` accessor would otherwise describe THIS
|
|
172
|
+
// function's frames, and the original's accessor cannot be transplanted
|
|
173
|
+
// (it reads the receiver's slot). Copy the rendered string instead, and
|
|
174
|
+
// only when it is one: a driver that stashed a non-string there keeps its
|
|
175
|
+
// own descriptor rather than having a lie written over it.
|
|
176
|
+
const originalStack = cause.stack;
|
|
177
|
+
if (typeof originalStack === 'string') {
|
|
178
|
+
descriptors.stack = { value: originalStack, writable: true, enumerable: false, configurable: true };
|
|
179
|
+
}
|
|
180
|
+
else if (descriptors.stack && typeof descriptors.stack.get === 'function') {
|
|
181
|
+
// An own accessor bound to the ORIGINAL receiver would return undefined
|
|
182
|
+
// here; drop it and let the clone keep its own working one.
|
|
183
|
+
delete descriptors.stack;
|
|
184
|
+
}
|
|
185
|
+
Object.defineProperties(clone, descriptors);
|
|
186
|
+
return clone;
|
|
187
|
+
}
|
|
188
|
+
catch {
|
|
189
|
+
// A cause whose descriptors cannot be replayed (an exotic proxy, a frozen
|
|
190
|
+
// prototype chain) must not turn a database error into a TypeError thrown
|
|
191
|
+
// from an error constructor. Dropping the cause entirely is the safe
|
|
192
|
+
// direction here: 'safe' mode's contract is that no row value escapes.
|
|
193
|
+
return undefined;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
76
196
|
/**
|
|
77
197
|
* Render a user-supplied `where` / `connect` target for a "no row found" error
|
|
78
198
|
* message, honoring the global {@link ErrorMessageMode}. In 'safe' mode (the
|
|
@@ -205,14 +325,30 @@ class ValidationError extends TurbineError {
|
|
|
205
325
|
exports.ValidationError = ValidationError;
|
|
206
326
|
/** Thrown when a database connection fails */
|
|
207
327
|
class ConnectionError extends TurbineError {
|
|
328
|
+
/**
|
|
329
|
+
* The driver code that produced this error: a Postgres SQLSTATE (`28P01`
|
|
330
|
+
* wrong password, `3D000` no such database, `08006` connection failure, ...)
|
|
331
|
+
* or a Node socket/TLS code (`ECONNREFUSED`, `CERT_HAS_EXPIRED`, ...).
|
|
332
|
+
*
|
|
333
|
+
* Exposed because E004 covers causes with very different remedies, and the
|
|
334
|
+
* alternative for a caller who needs to tell "wrong password" from "server
|
|
335
|
+
* down" is matching on `.message` text or reaching into `.cause`, both of
|
|
336
|
+
* which are exactly the untyped handling this error class exists to remove.
|
|
337
|
+
* Undefined when Turbine raised the error itself rather than wrapping a
|
|
338
|
+
* driver error (a malformed connection string, a subscription on an HTTP
|
|
339
|
+
* pool).
|
|
340
|
+
*/
|
|
341
|
+
sqlstate;
|
|
208
342
|
/**
|
|
209
343
|
* @param message human-readable connection failure description.
|
|
210
344
|
* @param options optional pg/driver `cause` to preserve, used when wrapping a
|
|
211
|
-
* connection-class driver error via `wrapPgError
|
|
345
|
+
* connection-class driver error via `wrapPgError`, plus the driver `code`
|
|
346
|
+
* that classified it.
|
|
212
347
|
*/
|
|
213
348
|
constructor(message, options) {
|
|
214
349
|
super(exports.TurbineErrorCode.CONNECTION, message, options);
|
|
215
350
|
this.name = 'ConnectionError';
|
|
351
|
+
this.sqlstate = options?.sqlstate;
|
|
216
352
|
}
|
|
217
353
|
}
|
|
218
354
|
exports.ConnectionError = ConnectionError;
|
|
@@ -270,9 +406,12 @@ class UniqueConstraintError extends TurbineError {
|
|
|
270
406
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
271
407
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
272
408
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
273
|
-
// message carries keys/constraint/column names only, the
|
|
274
|
-
// `.
|
|
275
|
-
//
|
|
409
|
+
// message carries keys/constraint/column names only, and the same goes
|
|
410
|
+
// for `.cause`, whose `detail` is redacted by the TurbineError base
|
|
411
|
+
// constructor (see redactCauseForMode: an unredacted cause put the
|
|
412
|
+
// values straight back into any log line that prints the error object).
|
|
413
|
+
// The structured `.columns`/`.constraint`/`.column` fields survive in
|
|
414
|
+
// both modes, they carry NAMES, never values.
|
|
276
415
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
277
416
|
if (detail)
|
|
278
417
|
message += `: ${detail}`;
|
|
@@ -298,9 +437,12 @@ class ForeignKeyError extends TurbineError {
|
|
|
298
437
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
299
438
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
300
439
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
301
|
-
// message carries keys/constraint/column names only, the
|
|
302
|
-
// `.
|
|
303
|
-
//
|
|
440
|
+
// message carries keys/constraint/column names only, and the same goes
|
|
441
|
+
// for `.cause`, whose `detail` is redacted by the TurbineError base
|
|
442
|
+
// constructor (see redactCauseForMode: an unredacted cause put the
|
|
443
|
+
// values straight back into any log line that prints the error object).
|
|
444
|
+
// The structured `.columns`/`.constraint`/`.column` fields survive in
|
|
445
|
+
// both modes, they carry NAMES, never values.
|
|
304
446
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
305
447
|
if (detail)
|
|
306
448
|
message += `: ${detail}`;
|
|
@@ -325,9 +467,12 @@ class NotNullViolationError extends TurbineError {
|
|
|
325
467
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
326
468
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
327
469
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
328
|
-
// message carries keys/constraint/column names only, the
|
|
329
|
-
// `.
|
|
330
|
-
//
|
|
470
|
+
// message carries keys/constraint/column names only, and the same goes
|
|
471
|
+
// for `.cause`, whose `detail` is redacted by the TurbineError base
|
|
472
|
+
// constructor (see redactCauseForMode: an unredacted cause put the
|
|
473
|
+
// values straight back into any log line that prints the error object).
|
|
474
|
+
// The structured `.columns`/`.constraint`/`.column` fields survive in
|
|
475
|
+
// both modes, they carry NAMES, never values.
|
|
331
476
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
332
477
|
if (detail)
|
|
333
478
|
message += `: ${detail}`;
|
|
@@ -419,9 +564,12 @@ class CheckConstraintError extends TurbineError {
|
|
|
419
564
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
420
565
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
421
566
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
422
|
-
// message carries keys/constraint/column names only, the
|
|
423
|
-
// `.
|
|
424
|
-
//
|
|
567
|
+
// message carries keys/constraint/column names only, and the same goes
|
|
568
|
+
// for `.cause`, whose `detail` is redacted by the TurbineError base
|
|
569
|
+
// constructor (see redactCauseForMode: an unredacted cause put the
|
|
570
|
+
// values straight back into any log line that prints the error object).
|
|
571
|
+
// The structured `.columns`/`.constraint`/`.column` fields survive in
|
|
572
|
+
// both modes, they carry NAMES, never values.
|
|
425
573
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
426
574
|
if (detail)
|
|
427
575
|
message += `: ${detail}`;
|
|
@@ -445,9 +593,12 @@ class ExclusionConstraintError extends TurbineError {
|
|
|
445
593
|
// PII-safe by default: the raw pg `detail` string contains the
|
|
446
594
|
// conflicting row VALUES (e.g. `Key (email)=(alice@x.com) already
|
|
447
595
|
// exists.`). Only append it in 'verbose' mode. In 'safe' mode the
|
|
448
|
-
// message carries keys/constraint/column names only, the
|
|
449
|
-
// `.
|
|
450
|
-
//
|
|
596
|
+
// message carries keys/constraint/column names only, and the same goes
|
|
597
|
+
// for `.cause`, whose `detail` is redacted by the TurbineError base
|
|
598
|
+
// constructor (see redactCauseForMode: an unredacted cause put the
|
|
599
|
+
// values straight back into any log line that prints the error object).
|
|
600
|
+
// The structured `.columns`/`.constraint`/`.column` fields survive in
|
|
601
|
+
// both modes, they carry NAMES, never values.
|
|
451
602
|
const detail = errorMessageMode === 'verbose' ? detailFromCause(cause) : undefined;
|
|
452
603
|
if (detail)
|
|
453
604
|
message += `: ${detail}`;
|
|
@@ -582,10 +733,12 @@ function parseColumnsFromDetail(detail) {
|
|
|
582
733
|
return m[1].split(',').map((s) => s.trim());
|
|
583
734
|
}
|
|
584
735
|
/**
|
|
585
|
-
* Connection-class error codes. Covers
|
|
586
|
-
* connection_exception,
|
|
587
|
-
*
|
|
588
|
-
*
|
|
736
|
+
* Connection-class error codes. Covers pg SQLSTATEs (class 08
|
|
737
|
+
* connection_exception, class 28 authorization refusals, invalid_catalog_name,
|
|
738
|
+
* plus a few class-53/57 admin/availability codes) and Node driver-level socket
|
|
739
|
+
* and TLS error codes that arrive on the same `.code` field when the connection
|
|
740
|
+
* never reaches (or never gets past) Postgres. All map to
|
|
741
|
+
* {@link ConnectionError} (E004).
|
|
589
742
|
*
|
|
590
743
|
* `57014` (query_canceled, a server-side `statement_timeout` cancellation) is
|
|
591
744
|
* intentionally NOT here: it maps to {@link TimeoutError} (E002) instead.
|
|
@@ -597,7 +750,22 @@ const CONNECTION_ERROR_CODES = new Set([
|
|
|
597
750
|
'08003', // connection_does_not_exist
|
|
598
751
|
'08004', // sqlserver_rejected_establishment_of_sqlconnection
|
|
599
752
|
'08006', // connection_failure
|
|
753
|
+
'08007', // transaction_resolution_unknown
|
|
600
754
|
'08P01', // protocol_violation
|
|
755
|
+
// pg SQLSTATE class 28: the server answered and REFUSED us. These are the
|
|
756
|
+
// most common first-run failures there are (wrong password, a pg_hba rule
|
|
757
|
+
// that does not cover this user/host), and they used to fall through
|
|
758
|
+
// wrapPgError untouched: the caller got a raw pg `DatabaseError` whose
|
|
759
|
+
// `.code` was `28P01`, i.e. a value from the SQLSTATE namespace on the SAME
|
|
760
|
+
// property Turbine puts `TURBINE_E0NN` in. Every `err.code.startsWith
|
|
761
|
+
// ('TURBINE_')` check, every `instanceof ConnectionError` catch, and the
|
|
762
|
+
// README's "every error is typed" guarantee silently missed the single
|
|
763
|
+
// failure a new user is most likely to hit.
|
|
764
|
+
'28000', // invalid_authorization_specification
|
|
765
|
+
'28P01', // invalid_password
|
|
766
|
+
// The server answered but the database named in the connection string does
|
|
767
|
+
// not exist. Same class of first-run mistake, same escape.
|
|
768
|
+
'3D000', // invalid_catalog_name
|
|
601
769
|
// pg SQLSTATE class 53/57 (server unavailable / shutting down)
|
|
602
770
|
'53300', // too_many_connections
|
|
603
771
|
'57P01', // admin_shutdown
|
|
@@ -608,8 +776,44 @@ const CONNECTION_ERROR_CODES = new Set([
|
|
|
608
776
|
'ECONNRESET',
|
|
609
777
|
'ETIMEDOUT',
|
|
610
778
|
'ENOTFOUND',
|
|
779
|
+
'EAI_AGAIN', // transient DNS failure
|
|
780
|
+
'EHOSTUNREACH',
|
|
781
|
+
'ENETUNREACH',
|
|
611
782
|
'EPIPE',
|
|
783
|
+
// Node TLS handshake failures. They can only happen while OPENING a
|
|
784
|
+
// connection, so classifying them as connection errors cannot mis-tag a
|
|
785
|
+
// query failure, and a managed Postgres with a private CA is the other
|
|
786
|
+
// first-run wall people hit.
|
|
787
|
+
'DEPTH_ZERO_SELF_SIGNED_CERT',
|
|
788
|
+
'SELF_SIGNED_CERT_IN_CHAIN',
|
|
789
|
+
'UNABLE_TO_VERIFY_LEAF_SIGNATURE',
|
|
790
|
+
'CERT_HAS_EXPIRED',
|
|
791
|
+
'ERR_TLS_CERT_ALTNAME_INVALID',
|
|
612
792
|
]);
|
|
793
|
+
/**
|
|
794
|
+
* Actionable next step per connection-class code, appended to the driver's own
|
|
795
|
+
* message. The driver message states WHAT happened ("password authentication
|
|
796
|
+
* failed for user \"postgres\""); these state what to do about it, which is the
|
|
797
|
+
* whole difference between a typed error and a raw one for a first-run failure.
|
|
798
|
+
*
|
|
799
|
+
* A code with no entry here simply gets no hint appended.
|
|
800
|
+
*/
|
|
801
|
+
const CONNECTION_ERROR_HINTS = {
|
|
802
|
+
'28P01': 'The server rejected the credentials. Check the password in your connection string (or DATABASE_URL), including any URL-encoding of special characters.',
|
|
803
|
+
'28000': "The server refused this user/host combination. Check the user name and the server's pg_hba.conf rules for the client address.",
|
|
804
|
+
'3D000': 'The database named in the connection string does not exist. Check the path segment after the host, and create the database if needed.',
|
|
805
|
+
'53300': "The server has no free connection slots. Lower `poolSize` or raise the server's max_connections.",
|
|
806
|
+
'57P03': 'The server is still starting up (or shutting down) and is not accepting connections yet.',
|
|
807
|
+
ECONNREFUSED: 'Nothing is listening on that host and port. Check the server is running and the port is right.',
|
|
808
|
+
ENOTFOUND: 'The host in the connection string could not be resolved. Check the host name, and that the connection string itself is well formed.',
|
|
809
|
+
EAI_AGAIN: 'DNS lookup for the host failed temporarily. Check network/DNS availability, then retry.',
|
|
810
|
+
ETIMEDOUT: 'The connection attempt timed out before the server answered. Check firewall/security-group rules and `connectionTimeoutMs`.',
|
|
811
|
+
DEPTH_ZERO_SELF_SIGNED_CERT: 'The server presented a certificate Node cannot verify. Supply the CA via `ssl: { ca }`.',
|
|
812
|
+
SELF_SIGNED_CERT_IN_CHAIN: 'The server presented a certificate Node cannot verify. Supply the CA via `ssl: { ca }`.',
|
|
813
|
+
UNABLE_TO_VERIFY_LEAF_SIGNATURE: 'The server presented a certificate Node cannot verify. Supply the CA via `ssl: { ca }`.',
|
|
814
|
+
CERT_HAS_EXPIRED: "The server's TLS certificate has expired. Renew it, or supply the correct CA via `ssl: { ca }`.",
|
|
815
|
+
ERR_TLS_CERT_ALTNAME_INVALID: "The server's TLS certificate does not cover the host you connected to. Check the host name in the connection string.",
|
|
816
|
+
};
|
|
613
817
|
/**
|
|
614
818
|
* Translate a pg driver error into a typed Turbine error.
|
|
615
819
|
* If the error doesn't match a known constraint code, returns it unchanged.
|
|
@@ -623,6 +827,8 @@ const CONNECTION_ERROR_CODES = new Set([
|
|
|
623
827
|
* 40P01 (deadlock_detected) -> DeadlockError (retryable)
|
|
624
828
|
* 40001 (serialization_failure) -> SerializationFailureError (retryable)
|
|
625
829
|
* 57014 (query_canceled) -> TimeoutError (server-side statement_timeout)
|
|
830
|
+
* 28P01 / 28000 (auth refused) -> ConnectionError, with a remediation hint
|
|
831
|
+
* 3D000 (no such database) -> ConnectionError, with a remediation hint
|
|
626
832
|
* connection-class codes -> ConnectionError (see CONNECTION_ERROR_CODES)
|
|
627
833
|
*
|
|
628
834
|
* The original pg error is preserved as `.cause` on the wrapped error.
|
|
@@ -687,9 +893,14 @@ function wrapPgError(err) {
|
|
|
687
893
|
default:
|
|
688
894
|
if (CONNECTION_ERROR_CODES.has(e.code)) {
|
|
689
895
|
const pgMessage = typeof e.message === 'string' && e.message.length > 0 ? e.message : undefined;
|
|
690
|
-
|
|
896
|
+
// Own-property lookup only: `e.code` is driver-controlled text, and a
|
|
897
|
+
// plain-object map would happily resolve `constructor` or `toString`
|
|
898
|
+
// to a function and interpolate it into the message.
|
|
899
|
+
const hint = Object.hasOwn(CONNECTION_ERROR_HINTS, e.code) ? CONNECTION_ERROR_HINTS[e.code] : undefined;
|
|
900
|
+
const head = pgMessage
|
|
691
901
|
? `[turbine] Database connection error: ${pgMessage}`
|
|
692
|
-
: `[turbine] Database connection error (${e.code})
|
|
902
|
+
: `[turbine] Database connection error (${e.code})`;
|
|
903
|
+
return new ConnectionError(hint ? `${head} (${e.code}) ${hint}` : head, { cause: err, sqlstate: e.code });
|
|
693
904
|
}
|
|
694
905
|
return err;
|
|
695
906
|
}
|
package/dist/cjs/index.d.ts
CHANGED
|
@@ -37,14 +37,14 @@ export { alloydb, cockroachdb, postgresql, timescale, yugabytedb } from './adapt
|
|
|
37
37
|
export { type Middleware, type MiddlewareNext, type MiddlewareParams, type PgCompatPool, type PgCompatPoolClient, type PgCompatQueryResult, type PlanCacheMode, type RetryOptions, TransactionClient, type TransactionOptions, TurbineClient, type TurbineConfig, type TurbineDriver, withRetry, } from './client.js';
|
|
38
38
|
export type { BuiltStatement, BulkInsertStatementInput, ColumnDefinitionInput, ColumnTypeInput, CreateIndexStatementInput, CreateTableStatementInput, Dialect, DialectIntrospector, DialectMigrator, DialectName, InsertStatementInput, IntrospectOptions as DialectIntrospectOptions, ResultStrategy, StreamableConnection, UpsertStatementInput, } from './dialect.js';
|
|
39
39
|
export { postgresDialect } from './dialect.js';
|
|
40
|
-
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, ReadOnlyError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
|
|
40
|
+
export { CheckConstraintError, CircularRelationError, ConnectionError, DeadlockError, type ErrorMessageMode, ExclusionConstraintError, ForeignKeyError, getErrorMessageMode, MigrationError, NotFoundError, NotNullViolationError, OptimisticLockError, PipelineError, type PipelineResultSlot, REDACTED_DETAIL, ReadOnlyError, RelationError, SerializationFailureError, setErrorMessageMode, TimeoutError, TurbineError, TurbineErrorCode, UniqueConstraintError, UnsupportedFeatureError, ValidationError, wrapPgError, } from './errors.js';
|
|
41
41
|
export { type GenerateOptions, generate } from './generate.js';
|
|
42
42
|
export { type IntrospectOptions, introspect } from './introspect.js';
|
|
43
43
|
export { executeNestedCreate, executeNestedUpdate, hasRelationFields, type NestedWriteContext, } from './nested-write.js';
|
|
44
44
|
export { HttpJsonSink, type HttpJsonSinkOptions, type MetricsFlushBatch, type MetricsFlushRow, type ObserveConfig, type ObserveHandle, type ObserveSink, PgMetricsSink, type PgMetricsSinkOptions, } from './observe.js';
|
|
45
45
|
export { executePipeline, type PipelineOptions, type PipelineResults, pipelineSupported } from './pipeline.js';
|
|
46
46
|
export { fingerprintPrismaSchema } from './prisma-schema-fingerprint.js';
|
|
47
|
-
export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderDirection, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
|
|
47
|
+
export { type AggregateArgs, type AggregateResult, type ArrayFilter, AUTO_ASSUMED_ROUND_TRIP_MS, AUTO_COUNT_BATCH_MIN_PARENT_ROWS, AUTO_JOIN_PENALTY_MS_PER_ROW, AUTO_TO_ONE_JOIN_MAX_ROWS, AUTO_TO_ONE_JOIN_ROWS_MAX, AUTO_TO_ONE_JOIN_ROWS_MIN, type ColumnRef, type ConnectOrCreateOp, type CountArgs, type CreateArgs, type CreateDataInput, type CreateManyArgs, type DeferredQuery, type DeleteArgs, type DeleteManyArgs, type FieldResult, type FindManyArgs, type FindManyStreamArgs, type FindUniqueArgs, type GlobalFilters, type GroupByAggregateSpec, type GroupByArgs, type GroupByDistinctOn, type GroupByResult, type HavingClause, type JsonFilter, type JsonPathAggregateTarget, type JsonPathGroupKey, type JsonPathOrderBy, type MiddlewareFn, type NestedCreateOp, type NestedUpdateOp, type NestedUpdateOpItem, type NestedUpsertOpItem, type OmitResult, type OrderByClause, type OrderByObject, type OrderDirection, type PrivilegeOption, type QueryEvent, type QueryEventListener, QueryInterface, type QueryResult, type RelationDescriptor, type RelationFilter, type RelationLoadStrategy, type RelationPickBy, type RelationPickOrderBy, type SelectResult, type SkipGlobalFilters, type TemporalInfinityReading, type TextSearchFilter, type TypedWithClause, UNSAFE, type Unsafe, type UpdateArgs, type UpdateDataInput, type UpdateInput, type UpdateManyArgs, type UpdateOperatorInput, type UpsertArgs, type VectorDistanceFilter, type VectorFilter, type VectorMetric, type VectorOrderBy, type VectorOrderByDistance, type WhereClause, type WhereOperator, type WhereValue, type WithClause, type WithOptions, type WithOrderByObject, type WithResult, } from './query/index.js';
|
|
48
48
|
export { type ActiveSubscription, type NotificationHandler, type Subscription, validateChannel } from './realtime.js';
|
|
49
49
|
export type { CheckMetadata, ColumnMetadata, IndexMetadata, PrismaCompatMap, PrismaModelMap, PrismaRelationMap, PrismaSchemaSource, ReferentialAction, RelationDef, SchemaMetadata, TableMetadata, } from './schema.js';
|
|
50
50
|
export { camelToSnake, isDateType, normalizeKeyColumns, pgArrayType, pgTypeToTs, singularize, snakeToCamel, snakeToPascal, withDbFieldNames, } from './schema.js';
|
package/dist/cjs/index.js
CHANGED
|
@@ -34,8 +34,8 @@
|
|
|
34
34
|
* ```
|
|
35
35
|
*/
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
exports.
|
|
38
|
-
exports.TypedSqlQuery = exports.buildTypedSql = exports.turbineHttp = exports.defineSeed = exports.schemaToSQLString = exports.schemaToSQL = exports.schemaPush = exports.schemaDiff = exports.DestructivePushRefusal = exports.schemaDefToMetadata = exports.table = exports.isDocFieldIndexDef = exports.defineSchema = exports.column = exports.ColumnBuilder = exports.applyManyToManyRelations = exports.withDbFieldNames = exports.snakeToPascal = exports.snakeToCamel = exports.singularize = exports.pgTypeToTs = exports.pgArrayType = exports.normalizeKeyColumns = exports.isDateType = exports.camelToSnake = void 0;
|
|
37
|
+
exports.QueryInterface = exports.AUTO_TO_ONE_JOIN_ROWS_MIN = exports.AUTO_TO_ONE_JOIN_ROWS_MAX = exports.AUTO_TO_ONE_JOIN_MAX_ROWS = exports.AUTO_JOIN_PENALTY_MS_PER_ROW = exports.AUTO_COUNT_BATCH_MIN_PARENT_ROWS = exports.AUTO_ASSUMED_ROUND_TRIP_MS = exports.fingerprintPrismaSchema = exports.pipelineSupported = exports.executePipeline = exports.PgMetricsSink = exports.HttpJsonSink = exports.hasRelationFields = exports.executeNestedUpdate = exports.executeNestedCreate = exports.introspect = exports.generate = exports.wrapPgError = exports.ValidationError = exports.UnsupportedFeatureError = exports.UniqueConstraintError = exports.TurbineErrorCode = exports.TurbineError = exports.TimeoutError = exports.setErrorMessageMode = exports.SerializationFailureError = exports.RelationError = exports.ReadOnlyError = exports.REDACTED_DETAIL = exports.PipelineError = exports.OptimisticLockError = exports.NotNullViolationError = exports.NotFoundError = exports.MigrationError = exports.getErrorMessageMode = exports.ForeignKeyError = exports.ExclusionConstraintError = exports.DeadlockError = exports.ConnectionError = exports.CircularRelationError = exports.CheckConstraintError = exports.postgresDialect = exports.withRetry = exports.TurbineClient = exports.TransactionClient = exports.yugabytedb = exports.timescale = exports.postgresql = exports.cockroachdb = exports.alloydb = void 0;
|
|
38
|
+
exports.TypedSqlQuery = exports.buildTypedSql = exports.turbineHttp = exports.defineSeed = exports.schemaToSQLString = exports.schemaToSQL = exports.schemaPush = exports.schemaDiff = exports.DestructivePushRefusal = exports.schemaDefToMetadata = exports.table = exports.isDocFieldIndexDef = exports.defineSchema = exports.column = exports.ColumnBuilder = exports.applyManyToManyRelations = exports.withDbFieldNames = exports.snakeToPascal = exports.snakeToCamel = exports.singularize = exports.pgTypeToTs = exports.pgArrayType = exports.normalizeKeyColumns = exports.isDateType = exports.camelToSnake = exports.validateChannel = exports.UNSAFE = void 0;
|
|
39
39
|
var index_js_1 = require("./adapters/index.js");
|
|
40
40
|
Object.defineProperty(exports, "alloydb", { enumerable: true, get: function () { return index_js_1.alloydb; } });
|
|
41
41
|
Object.defineProperty(exports, "cockroachdb", { enumerable: true, get: function () { return index_js_1.cockroachdb; } });
|
|
@@ -63,6 +63,7 @@ Object.defineProperty(exports, "NotFoundError", { enumerable: true, get: functio
|
|
|
63
63
|
Object.defineProperty(exports, "NotNullViolationError", { enumerable: true, get: function () { return errors_js_1.NotNullViolationError; } });
|
|
64
64
|
Object.defineProperty(exports, "OptimisticLockError", { enumerable: true, get: function () { return errors_js_1.OptimisticLockError; } });
|
|
65
65
|
Object.defineProperty(exports, "PipelineError", { enumerable: true, get: function () { return errors_js_1.PipelineError; } });
|
|
66
|
+
Object.defineProperty(exports, "REDACTED_DETAIL", { enumerable: true, get: function () { return errors_js_1.REDACTED_DETAIL; } });
|
|
66
67
|
Object.defineProperty(exports, "ReadOnlyError", { enumerable: true, get: function () { return errors_js_1.ReadOnlyError; } });
|
|
67
68
|
Object.defineProperty(exports, "RelationError", { enumerable: true, get: function () { return errors_js_1.RelationError; } });
|
|
68
69
|
Object.defineProperty(exports, "SerializationFailureError", { enumerable: true, get: function () { return errors_js_1.SerializationFailureError; } });
|
|
@@ -97,6 +98,9 @@ Object.defineProperty(exports, "pipelineSupported", { enumerable: true, get: fun
|
|
|
97
98
|
var prisma_schema_fingerprint_js_1 = require("./prisma-schema-fingerprint.js");
|
|
98
99
|
Object.defineProperty(exports, "fingerprintPrismaSchema", { enumerable: true, get: function () { return prisma_schema_fingerprint_js_1.fingerprintPrismaSchema; } });
|
|
99
100
|
// Query builder
|
|
101
|
+
// The privilege sentinel. `skipGlobalFilters` / `includePii` /
|
|
102
|
+
// `allowFullTableScan` are unlocked by THIS VALUE and nothing else, so a
|
|
103
|
+
// request body spread into query args cannot enable them (JSON has no symbols).
|
|
100
104
|
var index_js_2 = require("./query/index.js");
|
|
101
105
|
Object.defineProperty(exports, "AUTO_ASSUMED_ROUND_TRIP_MS", { enumerable: true, get: function () { return index_js_2.AUTO_ASSUMED_ROUND_TRIP_MS; } });
|
|
102
106
|
Object.defineProperty(exports, "AUTO_COUNT_BATCH_MIN_PARENT_ROWS", { enumerable: true, get: function () { return index_js_2.AUTO_COUNT_BATCH_MIN_PARENT_ROWS; } });
|
|
@@ -105,6 +109,7 @@ Object.defineProperty(exports, "AUTO_TO_ONE_JOIN_MAX_ROWS", { enumerable: true,
|
|
|
105
109
|
Object.defineProperty(exports, "AUTO_TO_ONE_JOIN_ROWS_MAX", { enumerable: true, get: function () { return index_js_2.AUTO_TO_ONE_JOIN_ROWS_MAX; } });
|
|
106
110
|
Object.defineProperty(exports, "AUTO_TO_ONE_JOIN_ROWS_MIN", { enumerable: true, get: function () { return index_js_2.AUTO_TO_ONE_JOIN_ROWS_MIN; } });
|
|
107
111
|
Object.defineProperty(exports, "QueryInterface", { enumerable: true, get: function () { return index_js_2.QueryInterface; } });
|
|
112
|
+
Object.defineProperty(exports, "UNSAFE", { enumerable: true, get: function () { return index_js_2.UNSAFE; } });
|
|
108
113
|
// Realtime, LISTEN/NOTIFY pub/sub
|
|
109
114
|
var realtime_js_1 = require("./realtime.js");
|
|
110
115
|
Object.defineProperty(exports, "validateChannel", { enumerable: true, get: function () { return realtime_js_1.validateChannel; } });
|
package/dist/cjs/pipeline.js
CHANGED
|
@@ -90,8 +90,21 @@ async function executePipeline(pool, queries, options) {
|
|
|
90
90
|
if (queries.length === 0) {
|
|
91
91
|
return [];
|
|
92
92
|
}
|
|
93
|
-
// Acquire a single client, reused for both capability check and execution
|
|
94
|
-
|
|
93
|
+
// Acquire a single client, reused for both capability check and execution.
|
|
94
|
+
// The checkout is wrapped because a connection-level failure (28P01 wrong
|
|
95
|
+
// password, an unverifiable TLS cert, ECONNREFUSED) happens HERE, before any
|
|
96
|
+
// query runs. Unwrapped it escapes as a raw pg DatabaseError whose `.code`
|
|
97
|
+
// holds a SQLSTATE, which is the same property Turbine puts TURBINE_E0NN in,
|
|
98
|
+
// so a caller switching on `.code` silently receives a value from a foreign
|
|
99
|
+
// namespace. The transaction and nested-write checkouts were wrapped for this
|
|
100
|
+
// reason; this was the last sibling still bare.
|
|
101
|
+
let client;
|
|
102
|
+
try {
|
|
103
|
+
client = await pool.connect();
|
|
104
|
+
}
|
|
105
|
+
catch (err) {
|
|
106
|
+
throw (0, errors_js_1.wrapPgError)(err);
|
|
107
|
+
}
|
|
95
108
|
try {
|
|
96
109
|
if ((0, pipeline_submittable_js_1.supportsExtendedPipeline)(client)) {
|
|
97
110
|
// Real pipeline path, uses extended-query protocol wire methods
|
package/dist/cjs/powql.d.ts
CHANGED
|
@@ -349,6 +349,18 @@ export declare class PowqlInterface<T extends object = Record<string, unknown>>
|
|
|
349
349
|
* contract): for identical cross-engine results pass `nulls: 'last'`
|
|
350
350
|
* explicitly on Postgres, which defaults nulls-first for `desc`.
|
|
351
351
|
*/
|
|
352
|
+
/**
|
|
353
|
+
* Validate one orderBy direction token and return the PowQL keyword.
|
|
354
|
+
*
|
|
355
|
+
* Every direction site on this engine used to be spelled
|
|
356
|
+
* `x === 'desc' ? 'desc' : 'asc'`, so `'DESC'`, a token the CORE explicitly
|
|
357
|
+
* accepts and honours, compiled to `asc` here: identical application code
|
|
358
|
+
* sorted one way on Postgres and silently the opposite way on PowDB. The rule
|
|
359
|
+
* is core's {@link assertDirectionToken}, reused rather than re-derived, so
|
|
360
|
+
* the two engines cannot drift again. `undefined` stays "not specified"
|
|
361
|
+
* (defaults asc); `null` is a VALUE and is refused like any other bad token.
|
|
362
|
+
*/
|
|
363
|
+
private dirKeyword;
|
|
352
364
|
private buildOrder;
|
|
353
365
|
/** Compile one {@link JsonPathOrderBy} entry to `order .col->$n asc` (+ optional numeric cast). */
|
|
354
366
|
private buildJsonPathOrder;
|