@zvndev/powdb-client 0.16.0 → 0.17.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 CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.0 - 2026-07-19
4
+
5
+ - **Typed wire error codes.** Servers >= 0.17.0 append a stable one-byte error
6
+ class to error frames; the client maps it onto `PowDBErrorCode` (`timeout`,
7
+ `size_exceeded`, `auth_failed`, ...) instead of collapsing every server
8
+ error to `query_failed`, and exposes the raw class as
9
+ `PowDBError.wireErrorClass`. Fully backward compatible: classless frames
10
+ from older servers behave exactly as before, and unknown future classes
11
+ fall back to `query_failed`. See the repo's `docs/errors.md` for the code
12
+ table.
13
+
3
14
  ## 0.16.0 - 2026-07-18
4
15
 
5
16
  - Version-alignment release in lockstep with workspace v0.16.0 (NUL-safe
@@ -26,6 +26,39 @@ export type PowDBErrorCode =
26
26
  | "timeout"
27
27
  /** Type coercion on a row failed (queryTyped). */
28
28
  | "type_coercion_failed";
29
+ /**
30
+ * Stable wire error classes appended by 0.17+ servers to Error frames
31
+ * (see docs/errors.md in the PowDB repo). These numeric values never change;
32
+ * new classes are only appended. Treat unknown values as `internal`.
33
+ */
34
+ export declare const WIRE_ERROR_CLASS: {
35
+ /** Unclassified or internal server error. */
36
+ readonly internal: 0;
37
+ /** The query text failed to lex or parse. */
38
+ readonly parse: 1;
39
+ /** Planning or execution failed (unknown table/column, type mismatch, ...). */
40
+ readonly execution: 2;
41
+ /** A time budget elapsed (query timeout, gate wait, idle timeout). */
42
+ readonly timeout: 3;
43
+ /** A memory or size limit was exceeded. */
44
+ readonly limit_exceeded: 4;
45
+ /** The server is read-only and the statement requires a writer. */
46
+ readonly readonly_refused: 5;
47
+ /** Authentication or database selection failed at CONNECT time. */
48
+ readonly auth_failed: 6;
49
+ /** Too many failed authentication attempts. */
50
+ readonly rate_limited: 7;
51
+ /** A constraint (e.g. unique index) rejected the write. */
52
+ readonly constraint_violation: 8;
53
+ /** Execution was cancelled cooperatively (client disconnect). */
54
+ readonly cancelled: 9;
55
+ };
56
+ /**
57
+ * Map a server wire error class to the client's `PowDBErrorCode` taxonomy.
58
+ * `undefined` (legacy server, no class byte) and unknown future bytes fall
59
+ * back to `"query_failed"`, preserving pre-class behavior.
60
+ */
61
+ export declare function errorCodeForWireClass(errorClass: number | undefined): PowDBErrorCode;
29
62
  /**
30
63
  * All errors thrown by `@zvndev/powdb-client` are instances of this class.
31
64
  * Use `err.code` to branch; `err.cause` optionally carries the underlying
@@ -33,8 +66,16 @@ export type PowDBErrorCode =
33
66
  */
34
67
  export declare class PowDBError extends Error {
35
68
  readonly code: PowDBErrorCode;
69
+ /**
70
+ * The raw server wire error class (see `WIRE_ERROR_CLASS`), when the error
71
+ * came from a server Error frame that carried one. Finer-grained than
72
+ * `code` (e.g. it distinguishes readonly refusals and constraint
73
+ * violations); `undefined` for client-side errors and legacy servers.
74
+ */
75
+ readonly wireErrorClass?: number;
36
76
  constructor(message: string, code: PowDBErrorCode, options?: {
37
77
  cause?: unknown;
78
+ wireErrorClass?: number;
38
79
  });
39
80
  }
40
81
  /**
@@ -8,9 +8,55 @@
8
8
  * legitimate reason to handle them differently.
9
9
  */
10
10
  Object.defineProperty(exports, "__esModule", { value: true });
11
- exports.PowDBScriptError = exports.PowDBError = void 0;
11
+ exports.PowDBScriptError = exports.PowDBError = exports.WIRE_ERROR_CLASS = void 0;
12
+ exports.errorCodeForWireClass = errorCodeForWireClass;
12
13
  exports.isPowDBError = isPowDBError;
13
14
  exports.isPowDBScriptError = isPowDBScriptError;
15
+ /**
16
+ * Stable wire error classes appended by 0.17+ servers to Error frames
17
+ * (see docs/errors.md in the PowDB repo). These numeric values never change;
18
+ * new classes are only appended. Treat unknown values as `internal`.
19
+ */
20
+ exports.WIRE_ERROR_CLASS = {
21
+ /** Unclassified or internal server error. */
22
+ internal: 0,
23
+ /** The query text failed to lex or parse. */
24
+ parse: 1,
25
+ /** Planning or execution failed (unknown table/column, type mismatch, ...). */
26
+ execution: 2,
27
+ /** A time budget elapsed (query timeout, gate wait, idle timeout). */
28
+ timeout: 3,
29
+ /** A memory or size limit was exceeded. */
30
+ limit_exceeded: 4,
31
+ /** The server is read-only and the statement requires a writer. */
32
+ readonly_refused: 5,
33
+ /** Authentication or database selection failed at CONNECT time. */
34
+ auth_failed: 6,
35
+ /** Too many failed authentication attempts. */
36
+ rate_limited: 7,
37
+ /** A constraint (e.g. unique index) rejected the write. */
38
+ constraint_violation: 8,
39
+ /** Execution was cancelled cooperatively (client disconnect). */
40
+ cancelled: 9,
41
+ };
42
+ /**
43
+ * Map a server wire error class to the client's `PowDBErrorCode` taxonomy.
44
+ * `undefined` (legacy server, no class byte) and unknown future bytes fall
45
+ * back to `"query_failed"`, preserving pre-class behavior.
46
+ */
47
+ function errorCodeForWireClass(errorClass) {
48
+ switch (errorClass) {
49
+ case exports.WIRE_ERROR_CLASS.timeout:
50
+ return "timeout";
51
+ case exports.WIRE_ERROR_CLASS.limit_exceeded:
52
+ return "size_exceeded";
53
+ case exports.WIRE_ERROR_CLASS.auth_failed:
54
+ case exports.WIRE_ERROR_CLASS.rate_limited:
55
+ return "auth_failed";
56
+ default:
57
+ return "query_failed";
58
+ }
59
+ }
14
60
  /**
15
61
  * All errors thrown by `@zvndev/powdb-client` are instances of this class.
16
62
  * Use `err.code` to branch; `err.cause` optionally carries the underlying
@@ -18,11 +64,23 @@ exports.isPowDBScriptError = isPowDBScriptError;
18
64
  */
19
65
  class PowDBError extends Error {
20
66
  code;
67
+ /**
68
+ * The raw server wire error class (see `WIRE_ERROR_CLASS`), when the error
69
+ * came from a server Error frame that carried one. Finer-grained than
70
+ * `code` (e.g. it distinguishes readonly refusals and constraint
71
+ * violations); `undefined` for client-side errors and legacy servers.
72
+ */
73
+ wireErrorClass;
21
74
  constructor(message, code, options) {
22
75
  // ErrorOptions.cause is supported in Node 16.9+; we require Node 18+.
23
- super(message, options);
76
+ // Only forward `cause` when the caller supplied one, so a bare options
77
+ // object never materializes an own `cause: undefined` property.
78
+ super(message, options && "cause" in options
79
+ ? { cause: options.cause }
80
+ : undefined);
24
81
  this.name = "PowDBError";
25
82
  this.code = code;
83
+ this.wireErrorClass = options?.wireErrorClass;
26
84
  // Preserve the prototype chain across `target: es2020` and friends.
27
85
  Object.setPrototypeOf(this, PowDBError.prototype);
28
86
  }
@@ -19,7 +19,7 @@ import { EventEmitter } from "node:events";
19
19
  import { type NativeJson, type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus, type WireValue } from "./protocol.js";
20
20
  import { type TypedRow, type TypedSchema } from "./typed.js";
21
21
  /** Client library version. Compared to the server's reported version. */
22
- export declare const CLIENT_VERSION = "0.16.0";
22
+ export declare const CLIENT_VERSION = "0.17.0";
23
23
  /**
24
24
  * The maximum catalog format version this client can read. State this as the
25
25
  * `catalogVersion` in sync pull requests: the server accepts any replica whose
@@ -471,7 +471,7 @@ export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.
471
471
  export { Pool } from "./pool.js";
472
472
  export type { PoolOptions } from "./pool.js";
473
473
  export { splitStatements } from "./script.js";
474
- export { PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, } from "./errors.js";
474
+ export { errorCodeForWireClass, PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, WIRE_ERROR_CLASS, } from "./errors.js";
475
475
  export type { PowDBErrorCode } from "./errors.js";
476
476
  export { coerceValue, coerceRow, coerceRows, } from "./typed.js";
477
477
  export type { ColumnType, TypedSchema, TypedRow, Coerced, JsonValue, } from "./typed.js";
package/dist/cjs/index.js CHANGED
@@ -49,7 +49,7 @@ var __importStar = (this && this.__importStar) || (function () {
49
49
  };
50
50
  })();
51
51
  Object.defineProperty(exports, "__esModule", { value: true });
52
- exports.coerceRows = exports.coerceRow = exports.coerceValue = exports.isPowDBScriptError = exports.PowDBScriptError = exports.isPowDBError = exports.PowDBError = exports.splitStatements = exports.Pool = exports.PowqlIdent = exports.powql = exports.ident = exports.escapeIdent = exports.escapeLiteral = exports.MAX_SYNC_PULL_BYTES = exports.MAX_SYNC_PULL_UNITS = exports.MAX_SYNC_UNITS = exports.MAX_PARAMS = exports.MAX_COLUMNS = exports.MAX_ROWS = exports.MAX_PAYLOAD_SIZE = exports.tryDecode = exports.encode = exports.Client = exports.SUPPORTED_CATALOG_VERSION = exports.CLIENT_VERSION = void 0;
52
+ exports.coerceRows = exports.coerceRow = exports.coerceValue = exports.WIRE_ERROR_CLASS = exports.isPowDBScriptError = exports.PowDBScriptError = exports.isPowDBError = exports.PowDBError = exports.errorCodeForWireClass = exports.splitStatements = exports.Pool = exports.PowqlIdent = exports.powql = exports.ident = exports.escapeIdent = exports.escapeLiteral = exports.MAX_SYNC_PULL_BYTES = exports.MAX_SYNC_PULL_UNITS = exports.MAX_SYNC_UNITS = exports.MAX_PARAMS = exports.MAX_COLUMNS = exports.MAX_ROWS = exports.MAX_PAYLOAD_SIZE = exports.tryDecode = exports.encode = exports.Client = exports.SUPPORTED_CATALOG_VERSION = exports.CLIENT_VERSION = void 0;
53
53
  exports.assertServerCatalogVersionSupported = assertServerCatalogVersionSupported;
54
54
  const net = __importStar(require("node:net"));
55
55
  const tls = __importStar(require("node:tls"));
@@ -59,7 +59,7 @@ const errors_js_1 = require("./errors.js");
59
59
  const script_js_1 = require("./script.js");
60
60
  const typed_js_1 = require("./typed.js");
61
61
  /** Client library version. Compared to the server's reported version. */
62
- exports.CLIENT_VERSION = "0.16.0";
62
+ exports.CLIENT_VERSION = "0.17.0";
63
63
  /**
64
64
  * The maximum catalog format version this client can read. State this as the
65
65
  * `catalogVersion` in sync pull requests: the server accepts any replica whose
@@ -152,7 +152,7 @@ function nativeQueryResult(reply) {
152
152
  case "ResultMessage":
153
153
  return { kind: "message", message: reply.message };
154
154
  case "Error":
155
- throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
155
+ throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
156
156
  default:
157
157
  throw new errors_js_1.PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
158
158
  }
@@ -168,7 +168,7 @@ function rawNativeQueryResult(reply) {
168
168
  case "ResultMessage":
169
169
  return { kind: "message", message: reply.message };
170
170
  case "Error":
171
- throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
171
+ throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
172
172
  default:
173
173
  throw new errors_js_1.PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
174
174
  }
@@ -386,7 +386,7 @@ class Client extends node_events_1.EventEmitter {
386
386
  result = { kind: "message", message: reply.message };
387
387
  break;
388
388
  case "Error":
389
- throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
389
+ throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
390
390
  default:
391
391
  throw new errors_js_1.PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
392
392
  }
@@ -515,7 +515,7 @@ class Client extends node_events_1.EventEmitter {
515
515
  result = { kind: "message", message: reply.message };
516
516
  break;
517
517
  case "Error":
518
- throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, "query_failed");
518
+ throw new errors_js_1.PowDBError(`query failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
519
519
  default:
520
520
  throw new errors_js_1.PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
521
521
  }
@@ -572,7 +572,7 @@ class Client extends node_events_1.EventEmitter {
572
572
  try {
573
573
  const reply = await this.send({ type: "SyncStatus", replicaId }, opts);
574
574
  if (reply.type === "Error") {
575
- throw new errors_js_1.PowDBError(`sync status failed: ${reply.message}`, "query_failed");
575
+ throw new errors_js_1.PowDBError(`sync status failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
576
576
  }
577
577
  if (reply.type !== "SyncStatusResult") {
578
578
  throw new errors_js_1.PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -622,7 +622,7 @@ class Client extends node_events_1.EventEmitter {
622
622
  segmentFormatVersion: toU16(request.segmentFormatVersion, "segmentFormatVersion"),
623
623
  }, opts);
624
624
  if (reply.type === "Error") {
625
- throw new errors_js_1.PowDBError(`sync pull failed: ${reply.message}`, "query_failed");
625
+ throw new errors_js_1.PowDBError(`sync pull failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
626
626
  }
627
627
  if (reply.type !== "SyncPullResult") {
628
628
  throw new errors_js_1.PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -665,7 +665,7 @@ class Client extends node_events_1.EventEmitter {
665
665
  remoteLsn: toU64(request.remoteLsn, "remoteLsn"),
666
666
  }, opts);
667
667
  if (reply.type === "Error") {
668
- throw new errors_js_1.PowDBError(`sync ack failed: ${reply.message}`, "query_failed");
668
+ throw new errors_js_1.PowDBError(`sync ack failed: ${reply.message}`, (0, errors_js_1.errorCodeForWireClass)(reply.errorClass), { wireErrorClass: reply.errorClass });
669
669
  }
670
670
  if (reply.type !== "SyncAckResult") {
671
671
  throw new errors_js_1.PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -1226,10 +1226,12 @@ Object.defineProperty(exports, "Pool", { enumerable: true, get: function () { re
1226
1226
  var script_js_2 = require("./script.js");
1227
1227
  Object.defineProperty(exports, "splitStatements", { enumerable: true, get: function () { return script_js_2.splitStatements; } });
1228
1228
  var errors_js_2 = require("./errors.js");
1229
+ Object.defineProperty(exports, "errorCodeForWireClass", { enumerable: true, get: function () { return errors_js_2.errorCodeForWireClass; } });
1229
1230
  Object.defineProperty(exports, "PowDBError", { enumerable: true, get: function () { return errors_js_2.PowDBError; } });
1230
1231
  Object.defineProperty(exports, "isPowDBError", { enumerable: true, get: function () { return errors_js_2.isPowDBError; } });
1231
1232
  Object.defineProperty(exports, "PowDBScriptError", { enumerable: true, get: function () { return errors_js_2.PowDBScriptError; } });
1232
1233
  Object.defineProperty(exports, "isPowDBScriptError", { enumerable: true, get: function () { return errors_js_2.isPowDBScriptError; } });
1234
+ Object.defineProperty(exports, "WIRE_ERROR_CLASS", { enumerable: true, get: function () { return errors_js_2.WIRE_ERROR_CLASS; } });
1233
1235
  var typed_js_2 = require("./typed.js");
1234
1236
  Object.defineProperty(exports, "coerceValue", { enumerable: true, get: function () { return typed_js_2.coerceValue; } });
1235
1237
  Object.defineProperty(exports, "coerceRow", { enumerable: true, get: function () { return typed_js_2.coerceRow; } });
@@ -221,6 +221,7 @@ export type Message = {
221
221
  } | {
222
222
  type: "Error";
223
223
  message: string;
224
+ errorClass?: number;
224
225
  } | {
225
226
  type: "Disconnect";
226
227
  } | {
@@ -544,8 +544,16 @@ function decodePayload(msgType, payload) {
544
544
  }
545
545
  case exports.MSG_RESULT_MSG:
546
546
  return { type: "ResultMessage", message: decodeString(payload, cursor) };
547
- case exports.MSG_ERROR:
548
- return { type: "Error", message: decodeString(payload, cursor) };
547
+ case exports.MSG_ERROR: {
548
+ const message = decodeString(payload, cursor);
549
+ // 0.17+ servers append one stable error-class byte after the
550
+ // length-prefixed message (see docs/errors.md). Old servers send only
551
+ // the string, so absence means "no class".
552
+ if (cursor.pos < payload.length) {
553
+ return { type: "Error", message, errorClass: payload[cursor.pos] };
554
+ }
555
+ return { type: "Error", message };
556
+ }
549
557
  case exports.MSG_DISCONNECT:
550
558
  return { type: "Disconnect" };
551
559
  case exports.MSG_PING:
package/dist/errors.d.ts CHANGED
@@ -26,6 +26,39 @@ export type PowDBErrorCode =
26
26
  | "timeout"
27
27
  /** Type coercion on a row failed (queryTyped). */
28
28
  | "type_coercion_failed";
29
+ /**
30
+ * Stable wire error classes appended by 0.17+ servers to Error frames
31
+ * (see docs/errors.md in the PowDB repo). These numeric values never change;
32
+ * new classes are only appended. Treat unknown values as `internal`.
33
+ */
34
+ export declare const WIRE_ERROR_CLASS: {
35
+ /** Unclassified or internal server error. */
36
+ readonly internal: 0;
37
+ /** The query text failed to lex or parse. */
38
+ readonly parse: 1;
39
+ /** Planning or execution failed (unknown table/column, type mismatch, ...). */
40
+ readonly execution: 2;
41
+ /** A time budget elapsed (query timeout, gate wait, idle timeout). */
42
+ readonly timeout: 3;
43
+ /** A memory or size limit was exceeded. */
44
+ readonly limit_exceeded: 4;
45
+ /** The server is read-only and the statement requires a writer. */
46
+ readonly readonly_refused: 5;
47
+ /** Authentication or database selection failed at CONNECT time. */
48
+ readonly auth_failed: 6;
49
+ /** Too many failed authentication attempts. */
50
+ readonly rate_limited: 7;
51
+ /** A constraint (e.g. unique index) rejected the write. */
52
+ readonly constraint_violation: 8;
53
+ /** Execution was cancelled cooperatively (client disconnect). */
54
+ readonly cancelled: 9;
55
+ };
56
+ /**
57
+ * Map a server wire error class to the client's `PowDBErrorCode` taxonomy.
58
+ * `undefined` (legacy server, no class byte) and unknown future bytes fall
59
+ * back to `"query_failed"`, preserving pre-class behavior.
60
+ */
61
+ export declare function errorCodeForWireClass(errorClass: number | undefined): PowDBErrorCode;
29
62
  /**
30
63
  * All errors thrown by `@zvndev/powdb-client` are instances of this class.
31
64
  * Use `err.code` to branch; `err.cause` optionally carries the underlying
@@ -33,8 +66,16 @@ export type PowDBErrorCode =
33
66
  */
34
67
  export declare class PowDBError extends Error {
35
68
  readonly code: PowDBErrorCode;
69
+ /**
70
+ * The raw server wire error class (see `WIRE_ERROR_CLASS`), when the error
71
+ * came from a server Error frame that carried one. Finer-grained than
72
+ * `code` (e.g. it distinguishes readonly refusals and constraint
73
+ * violations); `undefined` for client-side errors and legacy servers.
74
+ */
75
+ readonly wireErrorClass?: number;
36
76
  constructor(message: string, code: PowDBErrorCode, options?: {
37
77
  cause?: unknown;
78
+ wireErrorClass?: number;
38
79
  });
39
80
  }
40
81
  /**
package/dist/errors.js CHANGED
@@ -6,6 +6,51 @@
6
6
  * is intentionally small — new codes are added only when a caller has a
7
7
  * legitimate reason to handle them differently.
8
8
  */
9
+ /**
10
+ * Stable wire error classes appended by 0.17+ servers to Error frames
11
+ * (see docs/errors.md in the PowDB repo). These numeric values never change;
12
+ * new classes are only appended. Treat unknown values as `internal`.
13
+ */
14
+ export const WIRE_ERROR_CLASS = {
15
+ /** Unclassified or internal server error. */
16
+ internal: 0,
17
+ /** The query text failed to lex or parse. */
18
+ parse: 1,
19
+ /** Planning or execution failed (unknown table/column, type mismatch, ...). */
20
+ execution: 2,
21
+ /** A time budget elapsed (query timeout, gate wait, idle timeout). */
22
+ timeout: 3,
23
+ /** A memory or size limit was exceeded. */
24
+ limit_exceeded: 4,
25
+ /** The server is read-only and the statement requires a writer. */
26
+ readonly_refused: 5,
27
+ /** Authentication or database selection failed at CONNECT time. */
28
+ auth_failed: 6,
29
+ /** Too many failed authentication attempts. */
30
+ rate_limited: 7,
31
+ /** A constraint (e.g. unique index) rejected the write. */
32
+ constraint_violation: 8,
33
+ /** Execution was cancelled cooperatively (client disconnect). */
34
+ cancelled: 9,
35
+ };
36
+ /**
37
+ * Map a server wire error class to the client's `PowDBErrorCode` taxonomy.
38
+ * `undefined` (legacy server, no class byte) and unknown future bytes fall
39
+ * back to `"query_failed"`, preserving pre-class behavior.
40
+ */
41
+ export function errorCodeForWireClass(errorClass) {
42
+ switch (errorClass) {
43
+ case WIRE_ERROR_CLASS.timeout:
44
+ return "timeout";
45
+ case WIRE_ERROR_CLASS.limit_exceeded:
46
+ return "size_exceeded";
47
+ case WIRE_ERROR_CLASS.auth_failed:
48
+ case WIRE_ERROR_CLASS.rate_limited:
49
+ return "auth_failed";
50
+ default:
51
+ return "query_failed";
52
+ }
53
+ }
9
54
  /**
10
55
  * All errors thrown by `@zvndev/powdb-client` are instances of this class.
11
56
  * Use `err.code` to branch; `err.cause` optionally carries the underlying
@@ -13,11 +58,23 @@
13
58
  */
14
59
  export class PowDBError extends Error {
15
60
  code;
61
+ /**
62
+ * The raw server wire error class (see `WIRE_ERROR_CLASS`), when the error
63
+ * came from a server Error frame that carried one. Finer-grained than
64
+ * `code` (e.g. it distinguishes readonly refusals and constraint
65
+ * violations); `undefined` for client-side errors and legacy servers.
66
+ */
67
+ wireErrorClass;
16
68
  constructor(message, code, options) {
17
69
  // ErrorOptions.cause is supported in Node 16.9+; we require Node 18+.
18
- super(message, options);
70
+ // Only forward `cause` when the caller supplied one, so a bare options
71
+ // object never materializes an own `cause: undefined` property.
72
+ super(message, options && "cause" in options
73
+ ? { cause: options.cause }
74
+ : undefined);
19
75
  this.name = "PowDBError";
20
76
  this.code = code;
77
+ this.wireErrorClass = options?.wireErrorClass;
21
78
  // Preserve the prototype chain across `target: es2020` and friends.
22
79
  Object.setPrototypeOf(this, PowDBError.prototype);
23
80
  }
package/dist/index.d.ts CHANGED
@@ -19,7 +19,7 @@ import { EventEmitter } from "node:events";
19
19
  import { type NativeJson, type SyncRepairAction, type WireRetainedUnit, type WireSyncStatus, type WireValue } from "./protocol.js";
20
20
  import { type TypedRow, type TypedSchema } from "./typed.js";
21
21
  /** Client library version. Compared to the server's reported version. */
22
- export declare const CLIENT_VERSION = "0.16.0";
22
+ export declare const CLIENT_VERSION = "0.17.0";
23
23
  /**
24
24
  * The maximum catalog format version this client can read. State this as the
25
25
  * `catalogVersion` in sync pull requests: the server accepts any replica whose
@@ -471,7 +471,7 @@ export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.
471
471
  export { Pool } from "./pool.js";
472
472
  export type { PoolOptions } from "./pool.js";
473
473
  export { splitStatements } from "./script.js";
474
- export { PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, } from "./errors.js";
474
+ export { errorCodeForWireClass, PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, WIRE_ERROR_CLASS, } from "./errors.js";
475
475
  export type { PowDBErrorCode } from "./errors.js";
476
476
  export { coerceValue, coerceRow, coerceRows, } from "./typed.js";
477
477
  export type { ColumnType, TypedSchema, TypedRow, Coerced, JsonValue, } from "./typed.js";
package/dist/index.js CHANGED
@@ -18,11 +18,11 @@ import * as net from "node:net";
18
18
  import * as tls from "node:tls";
19
19
  import { EventEmitter } from "node:events";
20
20
  import { encode, tryDecode, MAX_SYNC_PULL_BYTES, MAX_SYNC_PULL_UNITS, } from "./protocol.js";
21
- import { PowDBError, PowDBScriptError, isPowDBError } from "./errors.js";
21
+ import { errorCodeForWireClass, isPowDBError, PowDBError, PowDBScriptError, } from "./errors.js";
22
22
  import { splitStatements } from "./script.js";
23
23
  import { coerceRows, } from "./typed.js";
24
24
  /** Client library version. Compared to the server's reported version. */
25
- export const CLIENT_VERSION = "0.16.0";
25
+ export const CLIENT_VERSION = "0.17.0";
26
26
  /**
27
27
  * The maximum catalog format version this client can read. State this as the
28
28
  * `catalogVersion` in sync pull requests: the server accepts any replica whose
@@ -115,7 +115,7 @@ function nativeQueryResult(reply) {
115
115
  case "ResultMessage":
116
116
  return { kind: "message", message: reply.message };
117
117
  case "Error":
118
- throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
118
+ throw new PowDBError(`query failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
119
119
  default:
120
120
  throw new PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
121
121
  }
@@ -131,7 +131,7 @@ function rawNativeQueryResult(reply) {
131
131
  case "ResultMessage":
132
132
  return { kind: "message", message: reply.message };
133
133
  case "Error":
134
- throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
134
+ throw new PowDBError(`query failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
135
135
  default:
136
136
  throw new PowDBError(`unexpected reply to native query: ${reply.type}`, "protocol_error");
137
137
  }
@@ -349,7 +349,7 @@ export class Client extends EventEmitter {
349
349
  result = { kind: "message", message: reply.message };
350
350
  break;
351
351
  case "Error":
352
- throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
352
+ throw new PowDBError(`query failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
353
353
  default:
354
354
  throw new PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
355
355
  }
@@ -478,7 +478,7 @@ export class Client extends EventEmitter {
478
478
  result = { kind: "message", message: reply.message };
479
479
  break;
480
480
  case "Error":
481
- throw new PowDBError(`query failed: ${reply.message}`, "query_failed");
481
+ throw new PowDBError(`query failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
482
482
  default:
483
483
  throw new PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
484
484
  }
@@ -535,7 +535,7 @@ export class Client extends EventEmitter {
535
535
  try {
536
536
  const reply = await this.send({ type: "SyncStatus", replicaId }, opts);
537
537
  if (reply.type === "Error") {
538
- throw new PowDBError(`sync status failed: ${reply.message}`, "query_failed");
538
+ throw new PowDBError(`sync status failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
539
539
  }
540
540
  if (reply.type !== "SyncStatusResult") {
541
541
  throw new PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -585,7 +585,7 @@ export class Client extends EventEmitter {
585
585
  segmentFormatVersion: toU16(request.segmentFormatVersion, "segmentFormatVersion"),
586
586
  }, opts);
587
587
  if (reply.type === "Error") {
588
- throw new PowDBError(`sync pull failed: ${reply.message}`, "query_failed");
588
+ throw new PowDBError(`sync pull failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
589
589
  }
590
590
  if (reply.type !== "SyncPullResult") {
591
591
  throw new PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -628,7 +628,7 @@ export class Client extends EventEmitter {
628
628
  remoteLsn: toU64(request.remoteLsn, "remoteLsn"),
629
629
  }, opts);
630
630
  if (reply.type === "Error") {
631
- throw new PowDBError(`sync ack failed: ${reply.message}`, "query_failed");
631
+ throw new PowDBError(`sync ack failed: ${reply.message}`, errorCodeForWireClass(reply.errorClass), { wireErrorClass: reply.errorClass });
632
632
  }
633
633
  if (reply.type !== "SyncAckResult") {
634
634
  throw new PowDBError(`unexpected reply: ${reply.type}`, "protocol_error");
@@ -1171,5 +1171,5 @@ export { MAX_PAYLOAD_SIZE, MAX_ROWS, MAX_COLUMNS, MAX_PARAMS, MAX_SYNC_UNITS, MA
1171
1171
  export { escapeLiteral, escapeIdent, ident, powql, PowqlIdent, } from "./escape.js";
1172
1172
  export { Pool } from "./pool.js";
1173
1173
  export { splitStatements } from "./script.js";
1174
- export { PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, } from "./errors.js";
1174
+ export { errorCodeForWireClass, PowDBError, isPowDBError, PowDBScriptError, isPowDBScriptError, WIRE_ERROR_CLASS, } from "./errors.js";
1175
1175
  export { coerceValue, coerceRow, coerceRows, } from "./typed.js";
@@ -221,6 +221,7 @@ export type Message = {
221
221
  } | {
222
222
  type: "Error";
223
223
  message: string;
224
+ errorClass?: number;
224
225
  } | {
225
226
  type: "Disconnect";
226
227
  } | {
package/dist/protocol.js CHANGED
@@ -539,8 +539,16 @@ function decodePayload(msgType, payload) {
539
539
  }
540
540
  case MSG_RESULT_MSG:
541
541
  return { type: "ResultMessage", message: decodeString(payload, cursor) };
542
- case MSG_ERROR:
543
- return { type: "Error", message: decodeString(payload, cursor) };
542
+ case MSG_ERROR: {
543
+ const message = decodeString(payload, cursor);
544
+ // 0.17+ servers append one stable error-class byte after the
545
+ // length-prefixed message (see docs/errors.md). Old servers send only
546
+ // the string, so absence means "no class".
547
+ if (cursor.pos < payload.length) {
548
+ return { type: "Error", message, errorClass: payload[cursor.pos] };
549
+ }
550
+ return { type: "Error", message };
551
+ }
544
552
  case MSG_DISCONNECT:
545
553
  return { type: "Disconnect" };
546
554
  case MSG_PING:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zvndev/powdb-client",
3
- "version": "0.16.0",
3
+ "version": "0.17.0",
4
4
  "description": "TypeScript client for PowDB (PowQL wire protocol)",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.29.3",