@prisma/param-graph 7.4.0-integration-parameterization.10 → 7.4.0-integration-parameterization.12

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.
@@ -4,6 +4,94 @@
4
4
  * This module handles compact binary encoding/decoding of the param graph structure.
5
5
  * The format uses a hybrid approach: JSON string array for field names + binary blob
6
6
  * for structural data (nodes, edges, roots).
7
+ *
8
+ * ## Serialized Representation
9
+ *
10
+ * ```
11
+ * SerializedParamGraph {
12
+ * strings: string[] // String table (field names, enum names, root keys)
13
+ * graph: string // Base64url-encoded binary blob
14
+ * }
15
+ * ```
16
+ *
17
+ * ## Why Hybrid?
18
+ *
19
+ * - **Strings stay as JSON**: V8's JSON.parse is highly optimized for string arrays
20
+ * - **Structure goes binary**: Indices, flags, masks benefit from compact encoding
21
+ * - **Best of both**: Fast parsing + compact size where it matters
22
+ *
23
+ * ## Variable-Length Encoding
24
+ *
25
+ * All integer values (except fixed-size fields like `scalarMask` and `flags`) use
26
+ * unsigned LEB128 (Little Endian Base 128) variable-length encoding:
27
+ *
28
+ * - Values 0-127: 1 byte
29
+ * - Values 128-16383: 2 bytes
30
+ * - Values 16384-2097151: 3 bytes
31
+ * - And so on...
32
+ *
33
+ * Optional values use value+1 encoding: 0 means "none/undefined", N+1 means actual value N.
34
+ *
35
+ * ## Binary Blob Layout
36
+ *
37
+ * ```
38
+ * ┌───────────────────────────────────────────────────────────────────┐
39
+ * │ HEADER │
40
+ * ├───────────────────────────────────────────────────────────────────┤
41
+ * │ inputNodeCount: varuint │
42
+ * │ outputNodeCount: varuint │
43
+ * │ rootCount: varuint │
44
+ * └───────────────────────────────────────────────────────────────────┘
45
+ *
46
+ * ┌───────────────────────────────────────────────────────────────────┐
47
+ * │ INPUT NODES (repeated inputNodeCount times) │
48
+ * ├───────────────────────────────────────────────────────────────────┤
49
+ * │ edgeCount: varuint │
50
+ * │ edges[] │
51
+ * └───────────────────────────────────────────────────────────────────┘
52
+ *
53
+ * ┌───────────────────────────────────────────────────────────────────┐
54
+ * │ INPUT EDGE │
55
+ * ├───────────────────────────────────────────────────────────────────┤
56
+ * │ fieldIndex: varuint │
57
+ * │ scalarMask: u16 │
58
+ * │ childNodeId: varuint (0=none, N+1=actual) │
59
+ * │ enumNameIndex: varuint (0=none, N+1=actual) │
60
+ * │ flags: u8 │
61
+ * └───────────────────────────────────────────────────────────────────┘
62
+ *
63
+ * ┌───────────────────────────────────────────────────────────────────┐
64
+ * │ OUTPUT NODES (repeated outputNodeCount times) │
65
+ * ├───────────────────────────────────────────────────────────────────┤
66
+ * │ edgeCount: varuint │
67
+ * │ edges[] │
68
+ * └───────────────────────────────────────────────────────────────────┘
69
+ *
70
+ * ┌───────────────────────────────────────────────────────────────────┐
71
+ * │ OUTPUT EDGE │
72
+ * ├───────────────────────────────────────────────────────────────────┤
73
+ * │ fieldIndex: varuint │
74
+ * │ argsNodeId: varuint (0=none, N+1=actual) │
75
+ * │ outputNodeId: varuint (0=none, N+1=actual) │
76
+ * └───────────────────────────────────────────────────────────────────┘
77
+ *
78
+ * ┌───────────────────────────────────────────────────────────────────┐
79
+ * │ ROOTS (repeated rootCount times) │
80
+ * ├───────────────────────────────────────────────────────────────────┤
81
+ * │ keyIndex: varuint │
82
+ * │ argsNodeId: varuint (0=none, N+1=actual) │
83
+ * │ outputNodeId: varuint (0=none, N+1=actual) │
84
+ * └───────────────────────────────────────────────────────────────────┘
85
+ * ```
86
+ *
87
+ * ## Embedding in Generated Client
88
+ *
89
+ * ```js
90
+ * config.parameterizationSchema = {
91
+ * strings: JSON.parse('["where","id","email",...]'),
92
+ * graph: "base64url_encoded_binary_blob..."
93
+ * }
94
+ * ```
7
95
  */
8
96
  import type { ParamGraphData } from './types';
9
97
  /**
@@ -28,20 +28,22 @@ function serializeParamGraph(data) {
28
28
  function deserializeParamGraph(serialized) {
29
29
  return new Deserializer(serialized).deserialize();
30
30
  }
31
- const FORMAT_COMPACT = 0;
32
- const FORMAT_WIDE = 1;
33
- const NONE_16 = 65535;
34
- const NONE_32 = 4294967295;
35
- const MAX_COMPACT_INDEX = 65534;
36
31
  function encodeBase64url(bytes) {
37
32
  return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url");
38
33
  }
39
34
  function decodeBase64url(str) {
40
35
  return Buffer.from(str, "base64url");
41
36
  }
37
+ function varuintSize(value) {
38
+ let size = 1;
39
+ while (value >= 128) {
40
+ size++;
41
+ value >>>= 7;
42
+ }
43
+ return size;
44
+ }
42
45
  class Serializer {
43
46
  #data;
44
- #useWide;
45
47
  #buffer;
46
48
  #view;
47
49
  #offset = 0;
@@ -49,13 +51,6 @@ class Serializer {
49
51
  constructor(data) {
50
52
  this.#data = data;
51
53
  this.#rootKeys = Object.keys(data.roots);
52
- const maxIndex = Math.max(
53
- data.strings.length,
54
- data.inputNodes.length,
55
- data.outputNodes.length,
56
- this.#rootKeys.length
57
- );
58
- this.#useWide = maxIndex > MAX_COMPACT_INDEX;
59
54
  const size = this.#calculateBufferSize();
60
55
  this.#buffer = new ArrayBuffer(size);
61
56
  this.#view = new DataView(this.#buffer);
@@ -67,25 +62,18 @@ class Serializer {
67
62
  this.#writeRoots();
68
63
  return {
69
64
  strings: this.#data.strings,
70
- graph: encodeBase64url(new Uint8Array(this.#buffer))
65
+ graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset))
71
66
  };
72
67
  }
73
- get #wordSize() {
74
- return this.#useWide ? 4 : 2;
75
- }
76
- get #noneValue() {
77
- return this.#useWide ? NONE_32 : NONE_16;
78
- }
79
- #writeWord(value) {
80
- if (this.#useWide) {
81
- this.#view.setUint32(this.#offset, value, true);
82
- } else {
83
- this.#view.setUint16(this.#offset, value, true);
68
+ #writeVaruint(value) {
69
+ while (value >= 128) {
70
+ this.#view.setUint8(this.#offset++, value & 127 | 128);
71
+ value >>>= 7;
84
72
  }
85
- this.#offset += this.#wordSize;
73
+ this.#view.setUint8(this.#offset++, value);
86
74
  }
87
- #writeOptionalWord(value) {
88
- this.#writeWord(value ?? this.#noneValue);
75
+ #writeOptionalVaruint(value) {
76
+ this.#writeVaruint(value === void 0 ? 0 : value + 1);
89
77
  }
90
78
  #writeByte(value) {
91
79
  this.#view.setUint8(this.#offset, value);
@@ -95,58 +83,70 @@ class Serializer {
95
83
  this.#view.setUint16(this.#offset, value, true);
96
84
  this.#offset += 2;
97
85
  }
98
- #skip(bytes) {
99
- this.#offset += bytes;
100
- }
101
86
  #calculateBufferSize() {
102
- let size = 1;
103
- size += this.#useWide ? 12 : 6;
87
+ let size = 0;
88
+ size += varuintSize(this.#data.inputNodes.length);
89
+ size += varuintSize(this.#data.outputNodes.length);
90
+ size += varuintSize(this.#rootKeys.length);
104
91
  for (const node of this.#data.inputNodes) {
105
- size += this.#wordSize;
106
- const edgeCount = Object.keys(node.edges).length;
107
- size += edgeCount * (this.#useWide ? 20 : 10);
92
+ const fieldIndices = Object.keys(node.edges).map(Number);
93
+ size += varuintSize(fieldIndices.length);
94
+ for (const fieldIndex of fieldIndices) {
95
+ const edge = node.edges[fieldIndex];
96
+ size += varuintSize(fieldIndex);
97
+ size += 2;
98
+ size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1);
99
+ size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1);
100
+ size += 1;
101
+ }
108
102
  }
109
103
  for (const node of this.#data.outputNodes) {
110
- size += this.#wordSize;
111
- const edgeCount = Object.keys(node.edges).length;
112
- size += edgeCount * (this.#useWide ? 12 : 6);
104
+ const fieldIndices = Object.keys(node.edges).map(Number);
105
+ size += varuintSize(fieldIndices.length);
106
+ for (const fieldIndex of fieldIndices) {
107
+ const edge = node.edges[fieldIndex];
108
+ size += varuintSize(fieldIndex);
109
+ size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1);
110
+ size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1);
111
+ }
112
+ }
113
+ for (const key of this.#rootKeys) {
114
+ const root = this.#data.roots[key];
115
+ const keyIndex = this.#data.strings.indexOf(key);
116
+ size += varuintSize(keyIndex);
117
+ size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1);
118
+ size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1);
113
119
  }
114
- size += this.#rootKeys.length * (this.#useWide ? 12 : 6);
115
120
  return size;
116
121
  }
117
122
  #writeHeader() {
118
- this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
119
- this.#writeWord(this.#data.inputNodes.length);
120
- this.#writeWord(this.#data.outputNodes.length);
121
- this.#writeWord(this.#rootKeys.length);
123
+ this.#writeVaruint(this.#data.inputNodes.length);
124
+ this.#writeVaruint(this.#data.outputNodes.length);
125
+ this.#writeVaruint(this.#rootKeys.length);
122
126
  }
123
127
  #writeInputNodes() {
124
128
  for (const node of this.#data.inputNodes) {
125
129
  const fieldIndices = Object.keys(node.edges).map(Number);
126
- this.#writeWord(fieldIndices.length);
130
+ this.#writeVaruint(fieldIndices.length);
127
131
  for (const fieldIndex of fieldIndices) {
128
132
  const edge = node.edges[fieldIndex];
129
- this.#writeWord(fieldIndex);
133
+ this.#writeVaruint(fieldIndex);
130
134
  this.#writeU16(edge.scalarMask ?? 0);
131
- if (this.#useWide) {
132
- this.#skip(2);
133
- }
134
- this.#writeOptionalWord(edge.childNodeId);
135
- this.#writeOptionalWord(edge.enumNameIndex);
135
+ this.#writeOptionalVaruint(edge.childNodeId);
136
+ this.#writeOptionalVaruint(edge.enumNameIndex);
136
137
  this.#writeByte(edge.flags);
137
- this.#skip(this.#useWide ? 3 : 1);
138
138
  }
139
139
  }
140
140
  }
141
141
  #writeOutputNodes() {
142
142
  for (const node of this.#data.outputNodes) {
143
143
  const fieldIndices = Object.keys(node.edges).map(Number);
144
- this.#writeWord(fieldIndices.length);
144
+ this.#writeVaruint(fieldIndices.length);
145
145
  for (const fieldIndex of fieldIndices) {
146
146
  const edge = node.edges[fieldIndex];
147
- this.#writeWord(fieldIndex);
148
- this.#writeOptionalWord(edge.argsNodeId);
149
- this.#writeOptionalWord(edge.outputNodeId);
147
+ this.#writeVaruint(fieldIndex);
148
+ this.#writeOptionalVaruint(edge.argsNodeId);
149
+ this.#writeOptionalVaruint(edge.outputNodeId);
150
150
  }
151
151
  }
152
152
  }
@@ -154,9 +154,12 @@ class Serializer {
154
154
  for (const key of this.#rootKeys) {
155
155
  const root = this.#data.roots[key];
156
156
  const keyIndex = this.#data.strings.indexOf(key);
157
- this.#writeWord(keyIndex);
158
- this.#writeOptionalWord(root.argsNodeId);
159
- this.#writeOptionalWord(root.outputNodeId);
157
+ if (keyIndex === -1) {
158
+ throw new Error(`Root key "${key}" not found in strings table`);
159
+ }
160
+ this.#writeVaruint(keyIndex);
161
+ this.#writeOptionalVaruint(root.argsNodeId);
162
+ this.#writeOptionalVaruint(root.outputNodeId);
160
163
  }
161
164
  }
162
165
  }
@@ -164,7 +167,6 @@ class Deserializer {
164
167
  #serialized;
165
168
  #view;
166
169
  #offset = 0;
167
- #useWide = false;
168
170
  constructor(serialized) {
169
171
  this.#serialized = serialized;
170
172
  const bytes = decodeBase64url(serialized.graph);
@@ -182,25 +184,20 @@ class Deserializer {
182
184
  roots
183
185
  };
184
186
  }
185
- get #wordSize() {
186
- return this.#useWide ? 4 : 2;
187
- }
188
- get #noneValue() {
189
- return this.#useWide ? NONE_32 : NONE_16;
190
- }
191
- #readWord() {
192
- let value;
193
- if (this.#useWide) {
194
- value = this.#view.getUint32(this.#offset, true);
195
- } else {
196
- value = this.#view.getUint16(this.#offset, true);
197
- }
198
- this.#offset += this.#wordSize;
187
+ #readVaruint() {
188
+ let value = 0;
189
+ let shift = 0;
190
+ let byte;
191
+ do {
192
+ byte = this.#view.getUint8(this.#offset++);
193
+ value |= (byte & 127) << shift;
194
+ shift += 7;
195
+ } while (byte >= 128);
199
196
  return value;
200
197
  }
201
- #readOptionalWord() {
202
- const value = this.#readWord();
203
- return value === this.#noneValue ? void 0 : value;
198
+ #readOptionalVaruint() {
199
+ const value = this.#readVaruint();
200
+ return value === 0 ? void 0 : value - 1;
204
201
  }
205
202
  #readByte() {
206
203
  const value = this.#view.getUint8(this.#offset);
@@ -212,32 +209,23 @@ class Deserializer {
212
209
  this.#offset += 2;
213
210
  return value;
214
211
  }
215
- #skip(bytes) {
216
- this.#offset += bytes;
217
- }
218
212
  #readHeader() {
219
- const format = this.#readByte();
220
- this.#useWide = format === FORMAT_WIDE;
221
- const inputNodeCount = this.#readWord();
222
- const outputNodeCount = this.#readWord();
223
- const rootCount = this.#readWord();
213
+ const inputNodeCount = this.#readVaruint();
214
+ const outputNodeCount = this.#readVaruint();
215
+ const rootCount = this.#readVaruint();
224
216
  return { inputNodeCount, outputNodeCount, rootCount };
225
217
  }
226
218
  #readInputNodes(count) {
227
219
  const inputNodes = [];
228
220
  for (let i = 0; i < count; i++) {
229
- const edgeCount = this.#readWord();
221
+ const edgeCount = this.#readVaruint();
230
222
  const edges = {};
231
223
  for (let j = 0; j < edgeCount; j++) {
232
- const fieldIndex = this.#readWord();
224
+ const fieldIndex = this.#readVaruint();
233
225
  const scalarMask = this.#readU16();
234
- if (this.#useWide) {
235
- this.#skip(2);
236
- }
237
- const childNodeId = this.#readOptionalWord();
238
- const enumNameIndex = this.#readOptionalWord();
226
+ const childNodeId = this.#readOptionalVaruint();
227
+ const enumNameIndex = this.#readOptionalVaruint();
239
228
  const flags = this.#readByte();
240
- this.#skip(this.#useWide ? 3 : 1);
241
229
  const edge = { flags };
242
230
  if (scalarMask !== 0) edge.scalarMask = scalarMask;
243
231
  if (childNodeId !== void 0) edge.childNodeId = childNodeId;
@@ -251,12 +239,12 @@ class Deserializer {
251
239
  #readOutputNodes(count) {
252
240
  const outputNodes = [];
253
241
  for (let i = 0; i < count; i++) {
254
- const edgeCount = this.#readWord();
242
+ const edgeCount = this.#readVaruint();
255
243
  const edges = {};
256
244
  for (let j = 0; j < edgeCount; j++) {
257
- const fieldIndex = this.#readWord();
258
- const argsNodeId = this.#readOptionalWord();
259
- const outputNodeId = this.#readOptionalWord();
245
+ const fieldIndex = this.#readVaruint();
246
+ const argsNodeId = this.#readOptionalVaruint();
247
+ const outputNodeId = this.#readOptionalVaruint();
260
248
  const edge = {};
261
249
  if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId;
262
250
  if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId;
@@ -269,9 +257,9 @@ class Deserializer {
269
257
  #readRoots(count) {
270
258
  const roots = {};
271
259
  for (let i = 0; i < count; i++) {
272
- const keyIndex = this.#readWord();
273
- const argsNodeId = this.#readOptionalWord();
274
- const outputNodeId = this.#readOptionalWord();
260
+ const keyIndex = this.#readVaruint();
261
+ const argsNodeId = this.#readOptionalVaruint();
262
+ const outputNodeId = this.#readOptionalVaruint();
275
263
  const key = this.#serialized.strings[keyIndex];
276
264
  const root = {};
277
265
  if (argsNodeId !== void 0) root.argsNodeId = argsNodeId;
@@ -4,20 +4,22 @@ function serializeParamGraph(data) {
4
4
  function deserializeParamGraph(serialized) {
5
5
  return new Deserializer(serialized).deserialize();
6
6
  }
7
- const FORMAT_COMPACT = 0;
8
- const FORMAT_WIDE = 1;
9
- const NONE_16 = 65535;
10
- const NONE_32 = 4294967295;
11
- const MAX_COMPACT_INDEX = 65534;
12
7
  function encodeBase64url(bytes) {
13
8
  return Buffer.from(bytes.buffer, bytes.byteOffset, bytes.byteLength).toString("base64url");
14
9
  }
15
10
  function decodeBase64url(str) {
16
11
  return Buffer.from(str, "base64url");
17
12
  }
13
+ function varuintSize(value) {
14
+ let size = 1;
15
+ while (value >= 128) {
16
+ size++;
17
+ value >>>= 7;
18
+ }
19
+ return size;
20
+ }
18
21
  class Serializer {
19
22
  #data;
20
- #useWide;
21
23
  #buffer;
22
24
  #view;
23
25
  #offset = 0;
@@ -25,13 +27,6 @@ class Serializer {
25
27
  constructor(data) {
26
28
  this.#data = data;
27
29
  this.#rootKeys = Object.keys(data.roots);
28
- const maxIndex = Math.max(
29
- data.strings.length,
30
- data.inputNodes.length,
31
- data.outputNodes.length,
32
- this.#rootKeys.length
33
- );
34
- this.#useWide = maxIndex > MAX_COMPACT_INDEX;
35
30
  const size = this.#calculateBufferSize();
36
31
  this.#buffer = new ArrayBuffer(size);
37
32
  this.#view = new DataView(this.#buffer);
@@ -43,25 +38,18 @@ class Serializer {
43
38
  this.#writeRoots();
44
39
  return {
45
40
  strings: this.#data.strings,
46
- graph: encodeBase64url(new Uint8Array(this.#buffer))
41
+ graph: encodeBase64url(new Uint8Array(this.#buffer, 0, this.#offset))
47
42
  };
48
43
  }
49
- get #wordSize() {
50
- return this.#useWide ? 4 : 2;
51
- }
52
- get #noneValue() {
53
- return this.#useWide ? NONE_32 : NONE_16;
54
- }
55
- #writeWord(value) {
56
- if (this.#useWide) {
57
- this.#view.setUint32(this.#offset, value, true);
58
- } else {
59
- this.#view.setUint16(this.#offset, value, true);
44
+ #writeVaruint(value) {
45
+ while (value >= 128) {
46
+ this.#view.setUint8(this.#offset++, value & 127 | 128);
47
+ value >>>= 7;
60
48
  }
61
- this.#offset += this.#wordSize;
49
+ this.#view.setUint8(this.#offset++, value);
62
50
  }
63
- #writeOptionalWord(value) {
64
- this.#writeWord(value ?? this.#noneValue);
51
+ #writeOptionalVaruint(value) {
52
+ this.#writeVaruint(value === void 0 ? 0 : value + 1);
65
53
  }
66
54
  #writeByte(value) {
67
55
  this.#view.setUint8(this.#offset, value);
@@ -71,58 +59,70 @@ class Serializer {
71
59
  this.#view.setUint16(this.#offset, value, true);
72
60
  this.#offset += 2;
73
61
  }
74
- #skip(bytes) {
75
- this.#offset += bytes;
76
- }
77
62
  #calculateBufferSize() {
78
- let size = 1;
79
- size += this.#useWide ? 12 : 6;
63
+ let size = 0;
64
+ size += varuintSize(this.#data.inputNodes.length);
65
+ size += varuintSize(this.#data.outputNodes.length);
66
+ size += varuintSize(this.#rootKeys.length);
80
67
  for (const node of this.#data.inputNodes) {
81
- size += this.#wordSize;
82
- const edgeCount = Object.keys(node.edges).length;
83
- size += edgeCount * (this.#useWide ? 20 : 10);
68
+ const fieldIndices = Object.keys(node.edges).map(Number);
69
+ size += varuintSize(fieldIndices.length);
70
+ for (const fieldIndex of fieldIndices) {
71
+ const edge = node.edges[fieldIndex];
72
+ size += varuintSize(fieldIndex);
73
+ size += 2;
74
+ size += varuintSize(edge.childNodeId === void 0 ? 0 : edge.childNodeId + 1);
75
+ size += varuintSize(edge.enumNameIndex === void 0 ? 0 : edge.enumNameIndex + 1);
76
+ size += 1;
77
+ }
84
78
  }
85
79
  for (const node of this.#data.outputNodes) {
86
- size += this.#wordSize;
87
- const edgeCount = Object.keys(node.edges).length;
88
- size += edgeCount * (this.#useWide ? 12 : 6);
80
+ const fieldIndices = Object.keys(node.edges).map(Number);
81
+ size += varuintSize(fieldIndices.length);
82
+ for (const fieldIndex of fieldIndices) {
83
+ const edge = node.edges[fieldIndex];
84
+ size += varuintSize(fieldIndex);
85
+ size += varuintSize(edge.argsNodeId === void 0 ? 0 : edge.argsNodeId + 1);
86
+ size += varuintSize(edge.outputNodeId === void 0 ? 0 : edge.outputNodeId + 1);
87
+ }
88
+ }
89
+ for (const key of this.#rootKeys) {
90
+ const root = this.#data.roots[key];
91
+ const keyIndex = this.#data.strings.indexOf(key);
92
+ size += varuintSize(keyIndex);
93
+ size += varuintSize(root.argsNodeId === void 0 ? 0 : root.argsNodeId + 1);
94
+ size += varuintSize(root.outputNodeId === void 0 ? 0 : root.outputNodeId + 1);
89
95
  }
90
- size += this.#rootKeys.length * (this.#useWide ? 12 : 6);
91
96
  return size;
92
97
  }
93
98
  #writeHeader() {
94
- this.#writeByte(this.#useWide ? FORMAT_WIDE : FORMAT_COMPACT);
95
- this.#writeWord(this.#data.inputNodes.length);
96
- this.#writeWord(this.#data.outputNodes.length);
97
- this.#writeWord(this.#rootKeys.length);
99
+ this.#writeVaruint(this.#data.inputNodes.length);
100
+ this.#writeVaruint(this.#data.outputNodes.length);
101
+ this.#writeVaruint(this.#rootKeys.length);
98
102
  }
99
103
  #writeInputNodes() {
100
104
  for (const node of this.#data.inputNodes) {
101
105
  const fieldIndices = Object.keys(node.edges).map(Number);
102
- this.#writeWord(fieldIndices.length);
106
+ this.#writeVaruint(fieldIndices.length);
103
107
  for (const fieldIndex of fieldIndices) {
104
108
  const edge = node.edges[fieldIndex];
105
- this.#writeWord(fieldIndex);
109
+ this.#writeVaruint(fieldIndex);
106
110
  this.#writeU16(edge.scalarMask ?? 0);
107
- if (this.#useWide) {
108
- this.#skip(2);
109
- }
110
- this.#writeOptionalWord(edge.childNodeId);
111
- this.#writeOptionalWord(edge.enumNameIndex);
111
+ this.#writeOptionalVaruint(edge.childNodeId);
112
+ this.#writeOptionalVaruint(edge.enumNameIndex);
112
113
  this.#writeByte(edge.flags);
113
- this.#skip(this.#useWide ? 3 : 1);
114
114
  }
115
115
  }
116
116
  }
117
117
  #writeOutputNodes() {
118
118
  for (const node of this.#data.outputNodes) {
119
119
  const fieldIndices = Object.keys(node.edges).map(Number);
120
- this.#writeWord(fieldIndices.length);
120
+ this.#writeVaruint(fieldIndices.length);
121
121
  for (const fieldIndex of fieldIndices) {
122
122
  const edge = node.edges[fieldIndex];
123
- this.#writeWord(fieldIndex);
124
- this.#writeOptionalWord(edge.argsNodeId);
125
- this.#writeOptionalWord(edge.outputNodeId);
123
+ this.#writeVaruint(fieldIndex);
124
+ this.#writeOptionalVaruint(edge.argsNodeId);
125
+ this.#writeOptionalVaruint(edge.outputNodeId);
126
126
  }
127
127
  }
128
128
  }
@@ -130,9 +130,12 @@ class Serializer {
130
130
  for (const key of this.#rootKeys) {
131
131
  const root = this.#data.roots[key];
132
132
  const keyIndex = this.#data.strings.indexOf(key);
133
- this.#writeWord(keyIndex);
134
- this.#writeOptionalWord(root.argsNodeId);
135
- this.#writeOptionalWord(root.outputNodeId);
133
+ if (keyIndex === -1) {
134
+ throw new Error(`Root key "${key}" not found in strings table`);
135
+ }
136
+ this.#writeVaruint(keyIndex);
137
+ this.#writeOptionalVaruint(root.argsNodeId);
138
+ this.#writeOptionalVaruint(root.outputNodeId);
136
139
  }
137
140
  }
138
141
  }
@@ -140,7 +143,6 @@ class Deserializer {
140
143
  #serialized;
141
144
  #view;
142
145
  #offset = 0;
143
- #useWide = false;
144
146
  constructor(serialized) {
145
147
  this.#serialized = serialized;
146
148
  const bytes = decodeBase64url(serialized.graph);
@@ -158,25 +160,20 @@ class Deserializer {
158
160
  roots
159
161
  };
160
162
  }
161
- get #wordSize() {
162
- return this.#useWide ? 4 : 2;
163
- }
164
- get #noneValue() {
165
- return this.#useWide ? NONE_32 : NONE_16;
166
- }
167
- #readWord() {
168
- let value;
169
- if (this.#useWide) {
170
- value = this.#view.getUint32(this.#offset, true);
171
- } else {
172
- value = this.#view.getUint16(this.#offset, true);
173
- }
174
- this.#offset += this.#wordSize;
163
+ #readVaruint() {
164
+ let value = 0;
165
+ let shift = 0;
166
+ let byte;
167
+ do {
168
+ byte = this.#view.getUint8(this.#offset++);
169
+ value |= (byte & 127) << shift;
170
+ shift += 7;
171
+ } while (byte >= 128);
175
172
  return value;
176
173
  }
177
- #readOptionalWord() {
178
- const value = this.#readWord();
179
- return value === this.#noneValue ? void 0 : value;
174
+ #readOptionalVaruint() {
175
+ const value = this.#readVaruint();
176
+ return value === 0 ? void 0 : value - 1;
180
177
  }
181
178
  #readByte() {
182
179
  const value = this.#view.getUint8(this.#offset);
@@ -188,32 +185,23 @@ class Deserializer {
188
185
  this.#offset += 2;
189
186
  return value;
190
187
  }
191
- #skip(bytes) {
192
- this.#offset += bytes;
193
- }
194
188
  #readHeader() {
195
- const format = this.#readByte();
196
- this.#useWide = format === FORMAT_WIDE;
197
- const inputNodeCount = this.#readWord();
198
- const outputNodeCount = this.#readWord();
199
- const rootCount = this.#readWord();
189
+ const inputNodeCount = this.#readVaruint();
190
+ const outputNodeCount = this.#readVaruint();
191
+ const rootCount = this.#readVaruint();
200
192
  return { inputNodeCount, outputNodeCount, rootCount };
201
193
  }
202
194
  #readInputNodes(count) {
203
195
  const inputNodes = [];
204
196
  for (let i = 0; i < count; i++) {
205
- const edgeCount = this.#readWord();
197
+ const edgeCount = this.#readVaruint();
206
198
  const edges = {};
207
199
  for (let j = 0; j < edgeCount; j++) {
208
- const fieldIndex = this.#readWord();
200
+ const fieldIndex = this.#readVaruint();
209
201
  const scalarMask = this.#readU16();
210
- if (this.#useWide) {
211
- this.#skip(2);
212
- }
213
- const childNodeId = this.#readOptionalWord();
214
- const enumNameIndex = this.#readOptionalWord();
202
+ const childNodeId = this.#readOptionalVaruint();
203
+ const enumNameIndex = this.#readOptionalVaruint();
215
204
  const flags = this.#readByte();
216
- this.#skip(this.#useWide ? 3 : 1);
217
205
  const edge = { flags };
218
206
  if (scalarMask !== 0) edge.scalarMask = scalarMask;
219
207
  if (childNodeId !== void 0) edge.childNodeId = childNodeId;
@@ -227,12 +215,12 @@ class Deserializer {
227
215
  #readOutputNodes(count) {
228
216
  const outputNodes = [];
229
217
  for (let i = 0; i < count; i++) {
230
- const edgeCount = this.#readWord();
218
+ const edgeCount = this.#readVaruint();
231
219
  const edges = {};
232
220
  for (let j = 0; j < edgeCount; j++) {
233
- const fieldIndex = this.#readWord();
234
- const argsNodeId = this.#readOptionalWord();
235
- const outputNodeId = this.#readOptionalWord();
221
+ const fieldIndex = this.#readVaruint();
222
+ const argsNodeId = this.#readOptionalVaruint();
223
+ const outputNodeId = this.#readOptionalVaruint();
236
224
  const edge = {};
237
225
  if (argsNodeId !== void 0) edge.argsNodeId = argsNodeId;
238
226
  if (outputNodeId !== void 0) edge.outputNodeId = outputNodeId;
@@ -245,9 +233,9 @@ class Deserializer {
245
233
  #readRoots(count) {
246
234
  const roots = {};
247
235
  for (let i = 0; i < count; i++) {
248
- const keyIndex = this.#readWord();
249
- const argsNodeId = this.#readOptionalWord();
250
- const outputNodeId = this.#readOptionalWord();
236
+ const keyIndex = this.#readVaruint();
237
+ const argsNodeId = this.#readOptionalVaruint();
238
+ const outputNodeId = this.#readOptionalVaruint();
251
239
  const key = this.#serialized.strings[keyIndex];
252
240
  const root = {};
253
241
  if (argsNodeId !== void 0) root.argsNodeId = argsNodeId;
@@ -83,4 +83,105 @@ var import_serialization = require("./serialization");
83
83
  const serialized = (0, import_serialization.serializeParamGraph)(data);
84
84
  (0, import_vitest.expect)(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/);
85
85
  });
86
+ (0, import_vitest.test)("handles large indices requiring multi-byte varints (128-16383)", () => {
87
+ const strings = Array.from({ length: 200 }, (_, i) => `field${i}`);
88
+ const data = {
89
+ strings,
90
+ inputNodes: [
91
+ {
92
+ edges: {
93
+ // Use indices that require 2-byte encoding (128+)
94
+ 128: { flags: 1, childNodeId: 150 },
95
+ 150: { flags: 2, enumNameIndex: 180 }
96
+ }
97
+ }
98
+ ],
99
+ outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }],
100
+ roots: { field0: { argsNodeId: 0, outputNodeId: 0 } }
101
+ };
102
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
103
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
104
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].flags).toBe(1);
105
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150);
106
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].flags).toBe(2);
107
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180);
108
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0);
109
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0);
110
+ });
111
+ (0, import_vitest.test)("handles very large indices requiring 3-byte varints (16384+)", () => {
112
+ const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`);
113
+ const data = {
114
+ strings,
115
+ inputNodes: [
116
+ {
117
+ edges: {
118
+ 16384: { flags: 1, childNodeId: 16500 }
119
+ }
120
+ }
121
+ ],
122
+ outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }],
123
+ roots: { f0: { argsNodeId: 0, outputNodeId: 0 } }
124
+ };
125
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
126
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
127
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].flags).toBe(1);
128
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500);
129
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0);
130
+ });
131
+ (0, import_vitest.test)("varint boundary values encode and decode correctly", () => {
132
+ const boundaryValues = [0, 1, 127, 128, 16383, 16384];
133
+ for (const value of boundaryValues) {
134
+ const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`);
135
+ const data = {
136
+ strings,
137
+ inputNodes: [{ edges: { [value]: { flags: 0 } } }],
138
+ outputNodes: [],
139
+ roots: { s0: {} }
140
+ };
141
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
142
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
143
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value]).toBeDefined();
144
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[value].flags).toBe(0);
145
+ }
146
+ });
147
+ (0, import_vitest.test)("optional value 0 is correctly distinguished from undefined", () => {
148
+ const data = {
149
+ strings: ["root", "field"],
150
+ inputNodes: [
151
+ { edges: { 1: { flags: 0, childNodeId: 0 } } }
152
+ // childNodeId = 0 (not undefined)
153
+ ],
154
+ outputNodes: [
155
+ { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } }
156
+ // both are 0 (not undefined)
157
+ ],
158
+ roots: { root: { argsNodeId: 0, outputNodeId: 0 } }
159
+ // both are 0 (not undefined)
160
+ };
161
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
162
+ const deserialized = (0, import_serialization.deserializeParamGraph)(serialized);
163
+ (0, import_vitest.expect)(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0);
164
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0);
165
+ (0, import_vitest.expect)(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0);
166
+ (0, import_vitest.expect)(deserialized.roots["root"].argsNodeId).toBe(0);
167
+ (0, import_vitest.expect)(deserialized.roots["root"].outputNodeId).toBe(0);
168
+ });
169
+ (0, import_vitest.test)("serialization produces more compact output with varint encoding", () => {
170
+ const data = {
171
+ strings: ["findMany", "create", "update", "where", "data", "id", "name", "email"],
172
+ inputNodes: [
173
+ { edges: { 3: { flags: 1, scalarMask: 1 }, 4: { flags: 0, scalarMask: 2 } } },
174
+ { edges: { 5: { flags: 2 }, 6: { flags: 2 }, 7: { flags: 2 } } }
175
+ ],
176
+ outputNodes: [{ edges: { 5: { argsNodeId: 0 }, 6: {}, 7: {} } }],
177
+ roots: {
178
+ findMany: { argsNodeId: 0, outputNodeId: 0 },
179
+ create: { argsNodeId: 1, outputNodeId: 0 },
180
+ update: { argsNodeId: 0, outputNodeId: 0 }
181
+ }
182
+ };
183
+ const serialized = (0, import_serialization.serializeParamGraph)(data);
184
+ const binarySize = Buffer.from(serialized.graph, "base64url").length;
185
+ (0, import_vitest.expect)(binarySize).toBeLessThan(80);
186
+ });
86
187
  });
@@ -82,4 +82,105 @@ describe("param-graph serialization", () => {
82
82
  const serialized = serializeParamGraph(data);
83
83
  expect(serialized.graph).toMatch(/^[A-Za-z0-9_-]+$/);
84
84
  });
85
+ test("handles large indices requiring multi-byte varints (128-16383)", () => {
86
+ const strings = Array.from({ length: 200 }, (_, i) => `field${i}`);
87
+ const data = {
88
+ strings,
89
+ inputNodes: [
90
+ {
91
+ edges: {
92
+ // Use indices that require 2-byte encoding (128+)
93
+ 128: { flags: 1, childNodeId: 150 },
94
+ 150: { flags: 2, enumNameIndex: 180 }
95
+ }
96
+ }
97
+ ],
98
+ outputNodes: [{ edges: { 199: { argsNodeId: 0, outputNodeId: 0 } } }],
99
+ roots: { field0: { argsNodeId: 0, outputNodeId: 0 } }
100
+ };
101
+ const serialized = serializeParamGraph(data);
102
+ const deserialized = deserializeParamGraph(serialized);
103
+ expect(deserialized.inputNodes[0].edges[128].flags).toBe(1);
104
+ expect(deserialized.inputNodes[0].edges[128].childNodeId).toBe(150);
105
+ expect(deserialized.inputNodes[0].edges[150].flags).toBe(2);
106
+ expect(deserialized.inputNodes[0].edges[150].enumNameIndex).toBe(180);
107
+ expect(deserialized.outputNodes[0].edges[199].argsNodeId).toBe(0);
108
+ expect(deserialized.outputNodes[0].edges[199].outputNodeId).toBe(0);
109
+ });
110
+ test("handles very large indices requiring 3-byte varints (16384+)", () => {
111
+ const strings = Array.from({ length: 17e3 }, (_, i) => `f${i}`);
112
+ const data = {
113
+ strings,
114
+ inputNodes: [
115
+ {
116
+ edges: {
117
+ 16384: { flags: 1, childNodeId: 16500 }
118
+ }
119
+ }
120
+ ],
121
+ outputNodes: [{ edges: { 16999: { argsNodeId: 0, outputNodeId: 0 } } }],
122
+ roots: { f0: { argsNodeId: 0, outputNodeId: 0 } }
123
+ };
124
+ const serialized = serializeParamGraph(data);
125
+ const deserialized = deserializeParamGraph(serialized);
126
+ expect(deserialized.inputNodes[0].edges[16384].flags).toBe(1);
127
+ expect(deserialized.inputNodes[0].edges[16384].childNodeId).toBe(16500);
128
+ expect(deserialized.outputNodes[0].edges[16999].argsNodeId).toBe(0);
129
+ });
130
+ test("varint boundary values encode and decode correctly", () => {
131
+ const boundaryValues = [0, 1, 127, 128, 16383, 16384];
132
+ for (const value of boundaryValues) {
133
+ const strings = Array.from({ length: Math.max(value + 1, 2) }, (_, i) => `s${i}`);
134
+ const data = {
135
+ strings,
136
+ inputNodes: [{ edges: { [value]: { flags: 0 } } }],
137
+ outputNodes: [],
138
+ roots: { s0: {} }
139
+ };
140
+ const serialized = serializeParamGraph(data);
141
+ const deserialized = deserializeParamGraph(serialized);
142
+ expect(deserialized.inputNodes[0].edges[value]).toBeDefined();
143
+ expect(deserialized.inputNodes[0].edges[value].flags).toBe(0);
144
+ }
145
+ });
146
+ test("optional value 0 is correctly distinguished from undefined", () => {
147
+ const data = {
148
+ strings: ["root", "field"],
149
+ inputNodes: [
150
+ { edges: { 1: { flags: 0, childNodeId: 0 } } }
151
+ // childNodeId = 0 (not undefined)
152
+ ],
153
+ outputNodes: [
154
+ { edges: { 1: { argsNodeId: 0, outputNodeId: 0 } } }
155
+ // both are 0 (not undefined)
156
+ ],
157
+ roots: { root: { argsNodeId: 0, outputNodeId: 0 } }
158
+ // both are 0 (not undefined)
159
+ };
160
+ const serialized = serializeParamGraph(data);
161
+ const deserialized = deserializeParamGraph(serialized);
162
+ expect(deserialized.inputNodes[0].edges[1].childNodeId).toBe(0);
163
+ expect(deserialized.outputNodes[0].edges[1].argsNodeId).toBe(0);
164
+ expect(deserialized.outputNodes[0].edges[1].outputNodeId).toBe(0);
165
+ expect(deserialized.roots["root"].argsNodeId).toBe(0);
166
+ expect(deserialized.roots["root"].outputNodeId).toBe(0);
167
+ });
168
+ test("serialization produces more compact output with varint encoding", () => {
169
+ const data = {
170
+ strings: ["findMany", "create", "update", "where", "data", "id", "name", "email"],
171
+ inputNodes: [
172
+ { edges: { 3: { flags: 1, scalarMask: 1 }, 4: { flags: 0, scalarMask: 2 } } },
173
+ { edges: { 5: { flags: 2 }, 6: { flags: 2 }, 7: { flags: 2 } } }
174
+ ],
175
+ outputNodes: [{ edges: { 5: { argsNodeId: 0 }, 6: {}, 7: {} } }],
176
+ roots: {
177
+ findMany: { argsNodeId: 0, outputNodeId: 0 },
178
+ create: { argsNodeId: 1, outputNodeId: 0 },
179
+ update: { argsNodeId: 0, outputNodeId: 0 }
180
+ }
181
+ };
182
+ const serialized = serializeParamGraph(data);
183
+ const binarySize = Buffer.from(serialized.graph, "base64url").length;
184
+ expect(binarySize).toBeLessThan(80);
185
+ });
85
186
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prisma/param-graph",
3
- "version": "7.4.0-integration-parameterization.10",
3
+ "version": "7.4.0-integration-parameterization.12",
4
4
  "description": "This package is intended for Prisma's internal use",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",