turbine-orm 0.62.0 → 0.62.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cjs/mssql.js CHANGED
@@ -161,8 +161,13 @@ function shapeResult(result) {
161
161
  * returns bigint as number within the JS safe-integer range). The `mssql`/tedious
162
162
  * driver returns BIGINT as a string to avoid precision loss, so a BIGINT IDENTITY
163
163
  * `id` would otherwise surface as `'1'` instead of `1`. Values outside the safe
164
- * range are left as strings (same as the Postgres path). Nested relations are
165
- * unaffected, `FOR JSON PATH` already renders BIGINT as a JSON number.
164
+ * range are left as strings (same as the Postgres path).
165
+ *
166
+ * Nested relations reach the same policy by a different route: since 0.51 the
167
+ * join strategy casts BIGINT to text (`mssqlDialect.jsonWireRule`) so FOR JSON
168
+ * cannot round it through an IEEE double, and that rule's `decode` re-applies
169
+ * the narrowing this function performs. The two must stay in step; when they
170
+ * were not, a child key came back `'1'` under `with` and `1` everywhere else.
166
171
  */
167
172
  function coerceBigIntColumns(rows, columns) {
168
173
  if (!columns || rows.length === 0)
@@ -535,12 +540,29 @@ exports.mssqlDialect = {
535
540
  const t = columnType.toLowerCase();
536
541
  // FOR JSON PATH renders BIGINT as a JSON number, which is an IEEE double:
537
542
  // a stored 9007199254740993 came back 9007199254740992 through the join
538
- // strategy, while a top-level read and the batched loader both returned
539
- // the exact decimal string. The tedious driver returns BIGINT as a STRING
540
- // unconditionally (verified for both small and large values), so carrying
541
- // text and keeping it reproduces the driver exactly.
543
+ // strategy. So the value is carried as text and re-decoded here.
544
+ //
545
+ // The DECODE re-applies the same safe-integer policy {@link
546
+ // coerceBigIntColumns} applies to the driver's own rows, which is what
547
+ // mysql and sqlite already do for their own 64-bit types. Keeping the text
548
+ // unconditionally (what this did before) reproduced the RAW driver value
549
+ // and not the value Turbine hands back: the driver returns BIGINT as a
550
+ // string, Turbine narrows a safe one to a number at the top level, and the
551
+ // batched loader goes through that same path. So a `with` under the join
552
+ // strategy was the ONE route that returned `'1'` where every other route
553
+ // returned `1`, i.e. the strategy silently changed the caller's value
554
+ // types. Above 2^53 all three keep the string, which is the point of
555
+ // carrying text at all.
542
556
  if (t === 'bigint') {
543
- return { sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`, decode: (value) => value };
557
+ return {
558
+ sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`,
559
+ decode: (value) => {
560
+ if (typeof value !== 'string' || !/^-?\d+$/.test(value))
561
+ return value;
562
+ const asNumber = Number(value);
563
+ return Number.isSafeInteger(asNumber) ? asNumber : value;
564
+ },
565
+ };
544
566
  }
545
567
  // Binary columns come out of FOR JSON PATH as BASE64 text ("AQL/") rather
546
568
  // than bytes. Style 2 converts to bare hex, which rebuilds exactly.
package/dist/mssql.js CHANGED
@@ -151,8 +151,13 @@ function shapeResult(result) {
151
151
  * returns bigint as number within the JS safe-integer range). The `mssql`/tedious
152
152
  * driver returns BIGINT as a string to avoid precision loss, so a BIGINT IDENTITY
153
153
  * `id` would otherwise surface as `'1'` instead of `1`. Values outside the safe
154
- * range are left as strings (same as the Postgres path). Nested relations are
155
- * unaffected, `FOR JSON PATH` already renders BIGINT as a JSON number.
154
+ * range are left as strings (same as the Postgres path).
155
+ *
156
+ * Nested relations reach the same policy by a different route: since 0.51 the
157
+ * join strategy casts BIGINT to text (`mssqlDialect.jsonWireRule`) so FOR JSON
158
+ * cannot round it through an IEEE double, and that rule's `decode` re-applies
159
+ * the narrowing this function performs. The two must stay in step; when they
160
+ * were not, a child key came back `'1'` under `with` and `1` everywhere else.
156
161
  */
157
162
  function coerceBigIntColumns(rows, columns) {
158
163
  if (!columns || rows.length === 0)
@@ -524,12 +529,29 @@ export const mssqlDialect = {
524
529
  const t = columnType.toLowerCase();
525
530
  // FOR JSON PATH renders BIGINT as a JSON number, which is an IEEE double:
526
531
  // a stored 9007199254740993 came back 9007199254740992 through the join
527
- // strategy, while a top-level read and the batched loader both returned
528
- // the exact decimal string. The tedious driver returns BIGINT as a STRING
529
- // unconditionally (verified for both small and large values), so carrying
530
- // text and keeping it reproduces the driver exactly.
532
+ // strategy. So the value is carried as text and re-decoded here.
533
+ //
534
+ // The DECODE re-applies the same safe-integer policy {@link
535
+ // coerceBigIntColumns} applies to the driver's own rows, which is what
536
+ // mysql and sqlite already do for their own 64-bit types. Keeping the text
537
+ // unconditionally (what this did before) reproduced the RAW driver value
538
+ // and not the value Turbine hands back: the driver returns BIGINT as a
539
+ // string, Turbine narrows a safe one to a number at the top level, and the
540
+ // batched loader goes through that same path. So a `with` under the join
541
+ // strategy was the ONE route that returned `'1'` where every other route
542
+ // returned `1`, i.e. the strategy silently changed the caller's value
543
+ // types. Above 2^53 all three keep the string, which is the point of
544
+ // carrying text at all.
531
545
  if (t === 'bigint') {
532
- return { sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`, decode: (value) => value };
546
+ return {
547
+ sql: (ref) => `CAST(${ref} AS NVARCHAR(50))`,
548
+ decode: (value) => {
549
+ if (typeof value !== 'string' || !/^-?\d+$/.test(value))
550
+ return value;
551
+ const asNumber = Number(value);
552
+ return Number.isSafeInteger(asNumber) ? asNumber : value;
553
+ },
554
+ };
533
555
  }
534
556
  // Binary columns come out of FOR JSON PATH as BASE64 text ("AQL/") rather
535
557
  // than bytes. Style 2 converts to bare hex, which rebuilds exactly.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "turbine-orm",
3
- "version": "0.62.0",
3
+ "version": "0.62.1",
4
4
  "description": "Postgres-native TypeScript ORM, runs on Neon, Vercel Postgres, Cloudflare, Supabase. Streaming cursors, typed errors, single-query nested relations. One dependency, no WASM engine",
5
5
  "type": "module",
6
6
  "//exports": "Each subpath declares its types PER CONDITION. A single shared top-level \"types\" resolves to the ESM declarations for `require` too, which is TS1479 (\"is an ES module ... cannot be require()d\") for any CJS consumer on moduleResolution node16/nodenext. The require condition points at dist/cjs, which ships its own {\"type\":\"commonjs\"} package.json, so those declarations are CJS declarations. Gated in CI by publint + @arethetypeswrong/cli + a real .cts consumer typecheck (see the package-types job in ci.yml).",