@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.
- package/dist/serialization.d.ts +88 -0
- package/dist/serialization.js +88 -100
- package/dist/serialization.mjs +88 -100
- package/dist/serialization.test.js +101 -0
- package/dist/serialization.test.mjs +101 -0
- package/package.json +1 -1
package/dist/serialization.d.ts
CHANGED
|
@@ -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
|
/**
|
package/dist/serialization.js
CHANGED
|
@@ -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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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
|
|
73
|
+
this.#view.setUint8(this.#offset++, value);
|
|
86
74
|
}
|
|
87
|
-
#
|
|
88
|
-
this.#
|
|
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 =
|
|
103
|
-
size += this.#
|
|
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
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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.#
|
|
119
|
-
this.#
|
|
120
|
-
this.#
|
|
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.#
|
|
130
|
+
this.#writeVaruint(fieldIndices.length);
|
|
127
131
|
for (const fieldIndex of fieldIndices) {
|
|
128
132
|
const edge = node.edges[fieldIndex];
|
|
129
|
-
this.#
|
|
133
|
+
this.#writeVaruint(fieldIndex);
|
|
130
134
|
this.#writeU16(edge.scalarMask ?? 0);
|
|
131
|
-
|
|
132
|
-
|
|
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.#
|
|
144
|
+
this.#writeVaruint(fieldIndices.length);
|
|
145
145
|
for (const fieldIndex of fieldIndices) {
|
|
146
146
|
const edge = node.edges[fieldIndex];
|
|
147
|
-
this.#
|
|
148
|
-
this.#
|
|
149
|
-
this.#
|
|
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
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
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
|
-
#
|
|
202
|
-
const value = this.#
|
|
203
|
-
return 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
|
|
220
|
-
|
|
221
|
-
const
|
|
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.#
|
|
221
|
+
const edgeCount = this.#readVaruint();
|
|
230
222
|
const edges = {};
|
|
231
223
|
for (let j = 0; j < edgeCount; j++) {
|
|
232
|
-
const fieldIndex = this.#
|
|
224
|
+
const fieldIndex = this.#readVaruint();
|
|
233
225
|
const scalarMask = this.#readU16();
|
|
234
|
-
|
|
235
|
-
|
|
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.#
|
|
242
|
+
const edgeCount = this.#readVaruint();
|
|
255
243
|
const edges = {};
|
|
256
244
|
for (let j = 0; j < edgeCount; j++) {
|
|
257
|
-
const fieldIndex = this.#
|
|
258
|
-
const argsNodeId = this.#
|
|
259
|
-
const outputNodeId = this.#
|
|
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.#
|
|
273
|
-
const argsNodeId = this.#
|
|
274
|
-
const outputNodeId = this.#
|
|
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;
|
package/dist/serialization.mjs
CHANGED
|
@@ -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
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
49
|
+
this.#view.setUint8(this.#offset++, value);
|
|
62
50
|
}
|
|
63
|
-
#
|
|
64
|
-
this.#
|
|
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 =
|
|
79
|
-
size += this.#
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
-
|
|
87
|
-
|
|
88
|
-
|
|
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.#
|
|
95
|
-
this.#
|
|
96
|
-
this.#
|
|
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.#
|
|
106
|
+
this.#writeVaruint(fieldIndices.length);
|
|
103
107
|
for (const fieldIndex of fieldIndices) {
|
|
104
108
|
const edge = node.edges[fieldIndex];
|
|
105
|
-
this.#
|
|
109
|
+
this.#writeVaruint(fieldIndex);
|
|
106
110
|
this.#writeU16(edge.scalarMask ?? 0);
|
|
107
|
-
|
|
108
|
-
|
|
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.#
|
|
120
|
+
this.#writeVaruint(fieldIndices.length);
|
|
121
121
|
for (const fieldIndex of fieldIndices) {
|
|
122
122
|
const edge = node.edges[fieldIndex];
|
|
123
|
-
this.#
|
|
124
|
-
this.#
|
|
125
|
-
this.#
|
|
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
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
#
|
|
178
|
-
const value = this.#
|
|
179
|
-
return 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
|
|
196
|
-
|
|
197
|
-
const
|
|
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.#
|
|
197
|
+
const edgeCount = this.#readVaruint();
|
|
206
198
|
const edges = {};
|
|
207
199
|
for (let j = 0; j < edgeCount; j++) {
|
|
208
|
-
const fieldIndex = this.#
|
|
200
|
+
const fieldIndex = this.#readVaruint();
|
|
209
201
|
const scalarMask = this.#readU16();
|
|
210
|
-
|
|
211
|
-
|
|
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.#
|
|
218
|
+
const edgeCount = this.#readVaruint();
|
|
231
219
|
const edges = {};
|
|
232
220
|
for (let j = 0; j < edgeCount; j++) {
|
|
233
|
-
const fieldIndex = this.#
|
|
234
|
-
const argsNodeId = this.#
|
|
235
|
-
const outputNodeId = this.#
|
|
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.#
|
|
249
|
-
const argsNodeId = this.#
|
|
250
|
-
const outputNodeId = this.#
|
|
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.
|
|
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",
|