node-firebird 2.14.3 → 2.14.4

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 CHANGED
@@ -1558,14 +1558,15 @@ fb.attach(_connection, function (err, svc) {
1558
1558
 
1559
1559
  Node-Firebird defaults to `UTF-8` for database connections, but fully supports custom client character sets. You can set the connection encoding by specifying `options.encoding` (e.g. `'UTF8'`, `'WIN1252'`, `'ISO8859_1'`, `'LATIN1'`, `'ASCII'`, or `'NONE'`).
1560
1560
 
1561
- Commonly used Firebird character sets are automatically mapped to their corresponding Node.js Buffer encodings:
1561
+ Commonly used Firebird character sets are handled through the corresponding Node.js encoding or ICU codec:
1562
1562
 
1563
- | Firebird Character Set | Node.js Buffer Encoding | Description / Notes |
1564
- | ---------------------- | ----------------------- | ------------------- |
1565
- | `UTF8`, `UNICODE_FSS` | `utf8` | Unicode. Handles character-level truncation automatically based on charset width. |
1566
- | `WIN1252`, `ISO8859_1`, `LATIN1` | `latin1` | 8-bit European encodings. Safely decodes special accented characters. |
1567
- | `ASCII` | `ascii` | 7-bit ASCII. |
1568
- | `NONE` | `latin1` | Raw/unspecified character set. Treated as binary-safe 8-bit characters. |
1563
+ | Firebird Character Set | Node.js encoding / ICU codec | Description / Notes |
1564
+ | ---------------------- | ---------------------------- | ------------------- |
1565
+ | `UTF8`, `UNICODE_FSS` | `utf8` | Unicode. Handles character-level truncation automatically based on charset width. |
1566
+ | `WIN1252` | ICU `windows-1252` codec | Windows Western European encoding, including the printable characters in bytes `0x80`–`0x9F`. |
1567
+ | `ISO8859_1`, `LATIN1` | `latin1` | ISO-8859-1-compatible byte mapping; intentionally distinct from Windows-1252. |
1568
+ | `ASCII` | `ascii` | 7-bit ASCII. |
1569
+ | `NONE` | `latin1` | Raw/unspecified character set. Treated as binary-safe 8-bit characters. |
1569
1570
 
1570
1571
  Beyond Node's native encodings, the driver ships **codepage codecs** for the
1571
1572
  single-byte charsets (decode *and* encode — columns, parameters, SQL
@@ -1581,7 +1582,9 @@ await db.queryAsync('INSERT INTO T VALUES (?)', ['Привет']); // encoded as
1581
1582
  ```
1582
1583
 
1583
1584
  The codecs are built from Node's ICU tables at first use (present in every
1584
- official Node build). `attachOrCreate`/`create` honour `options.encoding`
1585
+ official Node build). If a constrained runtime does not provide a requested
1586
+ codec, the driver throws a descriptive error instead of silently falling back
1587
+ to UTF-8 and corrupting text. `attachOrCreate`/`create` honour `options.encoding`
1585
1588
  for the new database's default charset too. Accented characters and
1586
1589
  fixed-length `CHAR(N)` whitespace/truncation are handled automatically per
1587
1590
  the charset width — and single-byte columns (including charset `NONE`) are
@@ -1597,7 +1600,7 @@ var options = {
1597
1600
  database: 'win1252_db.fdb',
1598
1601
  user: 'SYSDBA',
1599
1602
  password: 'masterkey',
1600
- encoding: 'WIN1252' // Automatically maps to 'latin1' under the hood
1603
+ encoding: 'WIN1252' // Uses the WHATWG/ICU Windows-1252 codec
1601
1604
  };
1602
1605
 
1603
1606
  Firebird.attach(options, function (err, db) {
@@ -2296,7 +2299,7 @@ options.blobReadChunkSize = 65535;
2296
2299
 
2297
2300
  If your server and client are on the same host, this won't matter much — the slowdown is latency-bound, not throughput-bound.
2298
2301
 
2299
- #### How do I use an encoding other than UTF-8 (e.g. WIN1252/Latin1)?
2302
+ #### How do I use an encoding other than UTF-8 (e.g. WIN1252 or Latin1)?
2300
2303
 
2301
2304
  Set `options.encoding` — no source changes required (see [Character Set & Encoding Support](#character-set--encoding-support) for the full mapping table):
2302
2305
 
@@ -17,7 +17,9 @@ export interface TextCodec {
17
17
  }
18
18
  export declare function charsetWidthById(id: number | undefined): number;
19
19
  /**
20
- * Codec for a Firebird charset name, or null when the charset is unknown,
21
- * natively handled by Buffer, or the ICU tables are unavailable. Cached.
20
+ * Codec for a Firebird charset name, or null when the charset is unknown or
21
+ * natively handled by Buffer. A known codepage whose ICU table is unavailable
22
+ * throws instead of silently falling back to UTF-8. Successful and unknown
23
+ * lookups are cached; failures are not.
22
24
  */
23
25
  export declare function getCodec(charsetName: string | undefined): TextCodec | null;
@@ -17,6 +17,7 @@ exports.getCodec = getCodec;
17
17
  const ICU_LABELS = Object.freeze({
18
18
  WIN1250: 'windows-1250',
19
19
  WIN1251: 'windows-1251',
20
+ WIN1252: 'windows-1252',
20
21
  WIN1253: 'windows-1253',
21
22
  WIN1254: 'windows-1254',
22
23
  WIN1255: 'windows-1255',
@@ -60,30 +61,63 @@ function charsetWidthById(id) {
60
61
  return CHARSET_WIDTH_BY_ID[id] || 1;
61
62
  }
62
63
  const cache = new Map();
63
- function buildCodec(name) {
64
- const label = ICU_LABELS[name];
65
- if (!label) {
66
- return null;
64
+ /**
65
+ * WHATWG windows-1252 code points for bytes 0x80–0x9F (the only range
66
+ * where it differs from Latin-1). Node's TextDecoder cannot be trusted
67
+ * here: through at least Node 20 the 'windows-1252' label is routed
68
+ * through a latin1 fast path, decoding this range as C1 controls, so
69
+ * the WIN1252 table is built from this fixed spec table instead of the
70
+ * runtime decoder. Later Node majors agree with this table exactly.
71
+ */
72
+ const WIN1252_C1 = Object.freeze([
73
+ 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
74
+ 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
75
+ 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
76
+ 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
77
+ ]);
78
+ function buildByteToCodeTable(name, label) {
79
+ const toCode = new Uint16Array(256);
80
+ if (name === 'WIN1252') {
81
+ // Latin-1 identity outside 0x80–0x9F; spec table inside. Needs no
82
+ // ICU support at all, so WIN1252 works even on small-icu builds.
83
+ for (let i = 0; i < 256; i++) {
84
+ toCode[i] = (i & 0xE0) === 0x80 ? WIN1252_C1[i - 0x80] : i;
85
+ }
86
+ return toCode;
67
87
  }
68
88
  let decoder;
69
89
  try {
70
90
  decoder = new TextDecoder(label);
71
91
  }
72
92
  catch {
73
- // Node built with small-icu: legacy encodings unavailable
74
- return null;
93
+ // Falling through to DEFAULT_ENCODING (UTF-8) would silently write
94
+ // different bytes from the explicitly requested Firebird codepage.
95
+ // Official Node builds include these ICU tables; constrained builds
96
+ // must fail clearly instead of corrupting text.
97
+ throw new Error(`The requested Firebird encoding ${name} requires the ${label} ICU codec, ` +
98
+ 'but this Node.js runtime does not provide it. Use an official full-ICU ' +
99
+ 'Node.js build, or connect with encoding NONE and pass explicitly encoded Buffer values.');
75
100
  }
76
- // Build both directions from the decoder, one byte at a time — every
77
- // byte of a single-byte codepage maps to exactly one BMP character
78
- // (undefined bytes decode to U+FFFD, which is kept for decoding but
79
- // never used for the reverse map).
80
- const toCode = new Uint16Array(256);
81
- const toByte = new Map();
82
101
  const one = Buffer.alloc(1);
83
102
  for (let i = 0; i < 256; i++) {
84
103
  one[0] = i;
85
- const ch = decoder.decode(one);
86
- toCode[i] = ch.charCodeAt(0);
104
+ toCode[i] = decoder.decode(one).charCodeAt(0);
105
+ }
106
+ return toCode;
107
+ }
108
+ function buildCodec(name) {
109
+ const label = ICU_LABELS[name];
110
+ if (!label) {
111
+ return null;
112
+ }
113
+ // Build both directions from one byte→code table — every byte of a
114
+ // single-byte codepage maps to exactly one BMP character (undefined
115
+ // bytes decode to U+FFFD, which is kept for decoding but never used
116
+ // for the reverse map).
117
+ const toCode = buildByteToCodeTable(name, label);
118
+ const toByte = new Map();
119
+ for (let i = 0; i < 256; i++) {
120
+ const ch = String.fromCharCode(toCode[i]);
87
121
  if (ch !== '�' && !toByte.has(ch)) {
88
122
  toByte.set(ch, i);
89
123
  }
@@ -120,8 +154,10 @@ function buildCodec(name) {
120
154
  };
121
155
  }
122
156
  /**
123
- * Codec for a Firebird charset name, or null when the charset is unknown,
124
- * natively handled by Buffer, or the ICU tables are unavailable. Cached.
157
+ * Codec for a Firebird charset name, or null when the charset is unknown or
158
+ * natively handled by Buffer. A known codepage whose ICU table is unavailable
159
+ * throws instead of silently falling back to UTF-8. Successful and unknown
160
+ * lookups are cached; failures are not.
125
161
  */
126
162
  function getCodec(charsetName) {
127
163
  if (!charsetName) {
@@ -39,13 +39,13 @@ const EMPTY_BUFFER = Buffer.alloc(0);
39
39
  * We must decode raw bytes with the matching Node.js encoding so that
40
40
  * characters outside ASCII are reproduced correctly.
41
41
  *
42
- * Commonly used Firebird charsets not listed here fall back to the
43
- * connection-level DEFAULT_ENCODING (typically 'utf8').
42
+ * Other recognized single-byte character sets are handled by the ICU-backed
43
+ * codec path. Only unknown character-set names fall back to the
44
+ * connection-level DEFAULT_ENCODING, typically UTF-8.
44
45
  */
45
46
  const FirebirdToNodeEncoding = Object.freeze({
46
47
  UTF8: 'utf8',
47
48
  UNICODE_FSS: 'utf8',
48
- WIN1252: 'latin1',
49
49
  ISO8859_1: 'latin1',
50
50
  LATIN1: 'latin1',
51
51
  ASCII: 'ascii',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-firebird",
3
- "version": "2.14.3",
3
+ "version": "2.14.4",
4
4
  "description": "Pure JavaScript and Asynchronous Firebird client for Node.js.",
5
5
  "keywords": [
6
6
  "firebird",
@@ -21,6 +21,7 @@ export interface TextCodec {
21
21
  const ICU_LABELS: Readonly<Record<string, string>> = Object.freeze({
22
22
  WIN1250: 'windows-1250',
23
23
  WIN1251: 'windows-1251',
24
+ WIN1252: 'windows-1252',
24
25
  WIN1253: 'windows-1253',
25
26
  WIN1254: 'windows-1254',
26
27
  WIN1255: 'windows-1255',
@@ -68,30 +69,67 @@ export function charsetWidthById(id: number | undefined): number {
68
69
 
69
70
  const cache = new Map<string, TextCodec | null>();
70
71
 
71
- function buildCodec(name: string): TextCodec | null {
72
- const label = ICU_LABELS[name];
73
- if (!label) {
74
- return null;
72
+ /**
73
+ * WHATWG windows-1252 code points for bytes 0x80–0x9F (the only range
74
+ * where it differs from Latin-1). Node's TextDecoder cannot be trusted
75
+ * here: through at least Node 20 the 'windows-1252' label is routed
76
+ * through a latin1 fast path, decoding this range as C1 controls, so
77
+ * the WIN1252 table is built from this fixed spec table instead of the
78
+ * runtime decoder. Later Node majors agree with this table exactly.
79
+ */
80
+ const WIN1252_C1 = Object.freeze([
81
+ 0x20AC, 0x0081, 0x201A, 0x0192, 0x201E, 0x2026, 0x2020, 0x2021,
82
+ 0x02C6, 0x2030, 0x0160, 0x2039, 0x0152, 0x008D, 0x017D, 0x008F,
83
+ 0x0090, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014,
84
+ 0x02DC, 0x2122, 0x0161, 0x203A, 0x0153, 0x009D, 0x017E, 0x0178,
85
+ ]);
86
+
87
+ function buildByteToCodeTable(name: string, label: string): Uint16Array {
88
+ const toCode = new Uint16Array(256);
89
+ if (name === 'WIN1252') {
90
+ // Latin-1 identity outside 0x80–0x9F; spec table inside. Needs no
91
+ // ICU support at all, so WIN1252 works even on small-icu builds.
92
+ for (let i = 0; i < 256; i++) {
93
+ toCode[i] = (i & 0xE0) === 0x80 ? WIN1252_C1[i - 0x80] : i;
94
+ }
95
+ return toCode;
75
96
  }
76
97
  let decoder: TextDecoder;
77
98
  try {
78
99
  decoder = new TextDecoder(label);
79
100
  } catch {
80
- // Node built with small-icu: legacy encodings unavailable
101
+ // Falling through to DEFAULT_ENCODING (UTF-8) would silently write
102
+ // different bytes from the explicitly requested Firebird codepage.
103
+ // Official Node builds include these ICU tables; constrained builds
104
+ // must fail clearly instead of corrupting text.
105
+ throw new Error(
106
+ `The requested Firebird encoding ${name} requires the ${label} ICU codec, ` +
107
+ 'but this Node.js runtime does not provide it. Use an official full-ICU ' +
108
+ 'Node.js build, or connect with encoding NONE and pass explicitly encoded Buffer values.'
109
+ );
110
+ }
111
+ const one = Buffer.alloc(1);
112
+ for (let i = 0; i < 256; i++) {
113
+ one[0] = i;
114
+ toCode[i] = decoder.decode(one).charCodeAt(0);
115
+ }
116
+ return toCode;
117
+ }
118
+
119
+ function buildCodec(name: string): TextCodec | null {
120
+ const label = ICU_LABELS[name];
121
+ if (!label) {
81
122
  return null;
82
123
  }
83
124
 
84
- // Build both directions from the decoder, one byte at a time every
85
- // byte of a single-byte codepage maps to exactly one BMP character
86
- // (undefined bytes decode to U+FFFD, which is kept for decoding but
87
- // never used for the reverse map).
88
- const toCode = new Uint16Array(256);
125
+ // Build both directions from one byte→code table every byte of a
126
+ // single-byte codepage maps to exactly one BMP character (undefined
127
+ // bytes decode to U+FFFD, which is kept for decoding but never used
128
+ // for the reverse map).
129
+ const toCode = buildByteToCodeTable(name, label);
89
130
  const toByte = new Map<string, number>();
90
- const one = Buffer.alloc(1);
91
131
  for (let i = 0; i < 256; i++) {
92
- one[0] = i;
93
- const ch = decoder.decode(one);
94
- toCode[i] = ch.charCodeAt(0);
132
+ const ch = String.fromCharCode(toCode[i]);
95
133
  if (ch !== '�' && !toByte.has(ch)) {
96
134
  toByte.set(ch, i);
97
135
  }
@@ -130,8 +168,10 @@ function buildCodec(name: string): TextCodec | null {
130
168
  }
131
169
 
132
170
  /**
133
- * Codec for a Firebird charset name, or null when the charset is unknown,
134
- * natively handled by Buffer, or the ICU tables are unavailable. Cached.
171
+ * Codec for a Firebird charset name, or null when the charset is unknown or
172
+ * natively handled by Buffer. A known codepage whose ICU table is unavailable
173
+ * throws instead of silently falling back to UTF-8. Successful and unknown
174
+ * lookups are cached; failures are not.
135
175
  */
136
176
  export function getCodec(charsetName: string | undefined): TextCodec | null {
137
177
  if (!charsetName) {
@@ -29,13 +29,13 @@ const EMPTY_BUFFER = Buffer.alloc(0);
29
29
  * We must decode raw bytes with the matching Node.js encoding so that
30
30
  * characters outside ASCII are reproduced correctly.
31
31
  *
32
- * Commonly used Firebird charsets not listed here fall back to the
33
- * connection-level DEFAULT_ENCODING (typically 'utf8').
32
+ * Other recognized single-byte character sets are handled by the ICU-backed
33
+ * codec path. Only unknown character-set names fall back to the
34
+ * connection-level DEFAULT_ENCODING, typically UTF-8.
34
35
  */
35
36
  const FirebirdToNodeEncoding: Readonly<Record<string, string>> = Object.freeze({
36
37
  UTF8: 'utf8',
37
38
  UNICODE_FSS: 'utf8',
38
- WIN1252: 'latin1',
39
39
  ISO8859_1: 'latin1',
40
40
  LATIN1: 'latin1',
41
41
  ASCII: 'ascii',