tersejson 0.2.1 → 0.3.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.
@@ -0,0 +1,451 @@
1
+ // src/types.ts
2
+ function isTersePayload(value) {
3
+ return typeof value === "object" && value !== null && "__terse__" in value && value.__terse__ === true && "v" in value && "k" in value && "d" in value;
4
+ }
5
+
6
+ // src/core.ts
7
+ function alphaGenerator(index) {
8
+ let key = "";
9
+ let remaining = index;
10
+ do {
11
+ key = String.fromCharCode(97 + remaining % 26) + key;
12
+ remaining = Math.floor(remaining / 26) - 1;
13
+ } while (remaining >= 0);
14
+ return key;
15
+ }
16
+ function numericGenerator(index) {
17
+ return String(index);
18
+ }
19
+ function alphanumericGenerator(index) {
20
+ const letterIndex = Math.floor(index / 9);
21
+ const numIndex = index % 9 + 1;
22
+ return alphaGenerator(letterIndex) + numIndex;
23
+ }
24
+ function shortGenerator(index) {
25
+ if (index === 0) return "_";
26
+ return alphaGenerator(index - 1);
27
+ }
28
+ function prefixedGenerator(prefix, style = "numeric") {
29
+ return (index) => {
30
+ if (style === "alpha") {
31
+ return prefix + alphaGenerator(index);
32
+ }
33
+ return prefix + index;
34
+ };
35
+ }
36
+ function createKeyGenerator(pattern) {
37
+ if (typeof pattern === "function") {
38
+ return { generator: pattern, name: "custom" };
39
+ }
40
+ if (typeof pattern === "string") {
41
+ switch (pattern) {
42
+ case "alpha":
43
+ return { generator: alphaGenerator, name: "alpha" };
44
+ case "numeric":
45
+ return { generator: numericGenerator, name: "numeric" };
46
+ case "alphanumeric":
47
+ return { generator: alphanumericGenerator, name: "alphanumeric" };
48
+ case "short":
49
+ return { generator: shortGenerator, name: "short" };
50
+ case "prefixed":
51
+ return { generator: prefixedGenerator("k"), name: "prefixed:k" };
52
+ default:
53
+ return { generator: alphaGenerator, name: "alpha" };
54
+ }
55
+ }
56
+ if (typeof pattern === "object" && "prefix" in pattern) {
57
+ return {
58
+ generator: prefixedGenerator(pattern.prefix, pattern.style || "numeric"),
59
+ name: `prefixed:${pattern.prefix}`
60
+ };
61
+ }
62
+ return { generator: alphaGenerator, name: "alpha" };
63
+ }
64
+ function resolveNestedDepth(handling, maxDepth) {
65
+ if (handling === void 0 || handling === "deep") {
66
+ return maxDepth;
67
+ }
68
+ if (handling === "shallow") {
69
+ return 1;
70
+ }
71
+ if (handling === "arrays") {
72
+ return maxDepth;
73
+ }
74
+ if (typeof handling === "number") {
75
+ return handling;
76
+ }
77
+ return maxDepth;
78
+ }
79
+ function collectKeys(data, options, currentDepth = 0) {
80
+ const keys = /* @__PURE__ */ new Set();
81
+ const { minKeyLength, maxDepth, nestedHandling, excludeKeys, includeKeys } = options;
82
+ if (currentDepth >= maxDepth) return keys;
83
+ const keyCounts = /* @__PURE__ */ new Map();
84
+ for (const item of data) {
85
+ if (typeof item !== "object" || item === null) continue;
86
+ for (const key of Object.keys(item)) {
87
+ if (excludeKeys?.includes(key)) continue;
88
+ const shouldInclude = includeKeys?.includes(key) || key.length >= minKeyLength;
89
+ if (shouldInclude) {
90
+ keys.add(key);
91
+ keyCounts.set(key, (keyCounts.get(key) || 0) + 1);
92
+ }
93
+ const value = item[key];
94
+ if (nestedHandling === "shallow") {
95
+ continue;
96
+ }
97
+ if (Array.isArray(value) && value.length > 0 && isCompressibleArray(value)) {
98
+ const nestedKeys = collectKeys(
99
+ value,
100
+ options,
101
+ currentDepth + 1
102
+ );
103
+ nestedKeys.forEach((k) => keys.add(k));
104
+ } else if (nestedHandling !== "arrays" && typeof value === "object" && value !== null && !Array.isArray(value)) {
105
+ const nestedKeys = collectKeys(
106
+ [value],
107
+ options,
108
+ currentDepth + 1
109
+ );
110
+ nestedKeys.forEach((k) => keys.add(k));
111
+ }
112
+ }
113
+ }
114
+ if (options.homogeneousOnly && data.length > 0) {
115
+ for (const [key, count] of keyCounts) {
116
+ if (count < data.length) {
117
+ keys.delete(key);
118
+ }
119
+ }
120
+ }
121
+ return keys;
122
+ }
123
+ function isCompressibleArray(data) {
124
+ if (!Array.isArray(data) || data.length === 0) return false;
125
+ return data.every(
126
+ (item) => typeof item === "object" && item !== null && !Array.isArray(item)
127
+ );
128
+ }
129
+ function compressObject(obj, keyToShort, maxDepth, currentDepth = 0) {
130
+ if (currentDepth >= maxDepth) return obj;
131
+ const compressed = {};
132
+ for (const [key, value] of Object.entries(obj)) {
133
+ const shortKey = keyToShort.get(key) ?? key;
134
+ if (Array.isArray(value) && isCompressibleArray(value)) {
135
+ compressed[shortKey] = value.map(
136
+ (item) => compressObject(
137
+ item,
138
+ keyToShort,
139
+ maxDepth,
140
+ currentDepth + 1
141
+ )
142
+ );
143
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
144
+ compressed[shortKey] = compressObject(
145
+ value,
146
+ keyToShort,
147
+ maxDepth,
148
+ currentDepth + 1
149
+ );
150
+ } else {
151
+ compressed[shortKey] = value;
152
+ }
153
+ }
154
+ return compressed;
155
+ }
156
+ function compress(data, options = {}) {
157
+ const {
158
+ minKeyLength = 3,
159
+ maxDepth = 10,
160
+ keyPattern = "alpha",
161
+ nestedHandling = "deep",
162
+ homogeneousOnly = false,
163
+ excludeKeys,
164
+ includeKeys
165
+ } = options;
166
+ const { generator, name: patternName } = createKeyGenerator(keyPattern);
167
+ const effectiveDepth = resolveNestedDepth(nestedHandling, maxDepth);
168
+ const allKeys = collectKeys(data, {
169
+ minKeyLength,
170
+ maxDepth: effectiveDepth,
171
+ nestedHandling,
172
+ excludeKeys,
173
+ includeKeys,
174
+ homogeneousOnly
175
+ });
176
+ const sortedKeys = Array.from(allKeys).sort();
177
+ const keyToShort = /* @__PURE__ */ new Map();
178
+ const keyMap = {};
179
+ sortedKeys.forEach((key, index) => {
180
+ const shortKey = generator(index);
181
+ if (shortKey.length < key.length) {
182
+ keyToShort.set(key, shortKey);
183
+ keyMap[shortKey] = key;
184
+ }
185
+ });
186
+ const compressed = data.map(
187
+ (item) => compressObject(item, keyToShort, effectiveDepth)
188
+ );
189
+ return {
190
+ __terse__: true,
191
+ v: 1,
192
+ k: keyMap,
193
+ d: compressed,
194
+ p: patternName
195
+ };
196
+ }
197
+ function createTerseProxy(compressed, keyMap) {
198
+ const originalToShort = new Map(
199
+ Object.entries(keyMap).map(([short, original]) => [original, short])
200
+ );
201
+ const handler = {
202
+ get(target, prop) {
203
+ if (typeof prop === "symbol") {
204
+ return Reflect.get(target, prop);
205
+ }
206
+ const shortKey = originalToShort.get(prop);
207
+ const actualKey = shortKey ?? prop;
208
+ const value = target[actualKey];
209
+ if (Array.isArray(value)) {
210
+ return value.map((item) => {
211
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
212
+ return createTerseProxy(item, keyMap);
213
+ }
214
+ return item;
215
+ });
216
+ }
217
+ if (typeof value === "object" && value !== null) {
218
+ return createTerseProxy(value, keyMap);
219
+ }
220
+ return value;
221
+ },
222
+ has(target, prop) {
223
+ if (typeof prop === "symbol") {
224
+ return Reflect.has(target, prop);
225
+ }
226
+ const shortKey = originalToShort.get(prop);
227
+ return (shortKey ?? prop) in target;
228
+ },
229
+ ownKeys(target) {
230
+ return Object.keys(target).map((shortKey) => keyMap[shortKey] ?? shortKey);
231
+ },
232
+ getOwnPropertyDescriptor(target, prop) {
233
+ if (typeof prop === "symbol") {
234
+ return Reflect.getOwnPropertyDescriptor(target, prop);
235
+ }
236
+ const shortKey = originalToShort.get(prop);
237
+ const actualKey = shortKey ?? prop;
238
+ const descriptor = Object.getOwnPropertyDescriptor(target, actualKey);
239
+ if (descriptor) {
240
+ return { ...descriptor, enumerable: true, configurable: true };
241
+ }
242
+ return void 0;
243
+ }
244
+ };
245
+ return new Proxy(compressed, handler);
246
+ }
247
+ function wrapWithProxy(payload) {
248
+ if (Array.isArray(payload.d)) {
249
+ return payload.d.map((item) => {
250
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
251
+ return createTerseProxy(item, payload.k);
252
+ }
253
+ return item;
254
+ });
255
+ }
256
+ if (typeof payload.d === "object" && payload.d !== null) {
257
+ return createTerseProxy(payload.d, payload.k);
258
+ }
259
+ return payload.d;
260
+ }
261
+
262
+ // src/server-memory.ts
263
+ var TerseCache = class {
264
+ constructor(options = {}) {
265
+ this.accessOrder = [];
266
+ this.cache = /* @__PURE__ */ new Map();
267
+ this.options = options;
268
+ }
269
+ /**
270
+ * Store data in compressed form
271
+ */
272
+ set(key, data, ttl) {
273
+ if (this.options.maxSize && this.cache.size >= this.options.maxSize) {
274
+ const oldestKey = this.accessOrder.shift();
275
+ if (oldestKey) {
276
+ this.cache.delete(oldestKey);
277
+ }
278
+ }
279
+ const payload = compress(data, this.options.compressOptions);
280
+ const effectiveTTL = ttl ?? this.options.defaultTTL;
281
+ const expiresAt = effectiveTTL ? Date.now() + effectiveTTL : void 0;
282
+ this.cache.set(key, { payload, expiresAt });
283
+ this.updateAccessOrder(key);
284
+ }
285
+ /**
286
+ * Get data wrapped in Proxy for lazy expansion.
287
+ * Only accessed fields are expanded from compressed form.
288
+ */
289
+ get(key) {
290
+ const entry = this.cache.get(key);
291
+ if (!entry) return void 0;
292
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
293
+ this.cache.delete(key);
294
+ this.removeFromAccessOrder(key);
295
+ return void 0;
296
+ }
297
+ this.updateAccessOrder(key);
298
+ return wrapWithProxy(entry.payload);
299
+ }
300
+ /**
301
+ * Get the raw compressed payload without expansion.
302
+ * Useful for forwarding to other services.
303
+ */
304
+ getRaw(key) {
305
+ const entry = this.cache.get(key);
306
+ if (!entry) return void 0;
307
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
308
+ this.cache.delete(key);
309
+ this.removeFromAccessOrder(key);
310
+ return void 0;
311
+ }
312
+ return entry.payload;
313
+ }
314
+ /**
315
+ * Check if key exists (and is not expired)
316
+ */
317
+ has(key) {
318
+ const entry = this.cache.get(key);
319
+ if (!entry) return false;
320
+ if (entry.expiresAt && Date.now() > entry.expiresAt) {
321
+ this.cache.delete(key);
322
+ this.removeFromAccessOrder(key);
323
+ return false;
324
+ }
325
+ return true;
326
+ }
327
+ /**
328
+ * Delete an entry
329
+ */
330
+ delete(key) {
331
+ this.removeFromAccessOrder(key);
332
+ return this.cache.delete(key);
333
+ }
334
+ /**
335
+ * Clear all entries
336
+ */
337
+ clear() {
338
+ this.cache.clear();
339
+ this.accessOrder = [];
340
+ }
341
+ /**
342
+ * Get current cache size
343
+ */
344
+ get size() {
345
+ return this.cache.size;
346
+ }
347
+ updateAccessOrder(key) {
348
+ this.removeFromAccessOrder(key);
349
+ this.accessOrder.push(key);
350
+ }
351
+ removeFromAccessOrder(key) {
352
+ const index = this.accessOrder.indexOf(key);
353
+ if (index > -1) {
354
+ this.accessOrder.splice(index, 1);
355
+ }
356
+ }
357
+ };
358
+ async function* compressStream(source, options = {}) {
359
+ const batchSize = options.batchSize ?? 100;
360
+ const { batchSize: _, ...compressOptions } = options;
361
+ let batch = [];
362
+ for await (const item of source) {
363
+ batch.push(item);
364
+ if (batch.length >= batchSize) {
365
+ yield compress(batch, compressOptions);
366
+ batch = [];
367
+ }
368
+ }
369
+ if (batch.length > 0) {
370
+ yield compress(batch, compressOptions);
371
+ }
372
+ }
373
+ function createTerseServiceClient(config) {
374
+ const {
375
+ baseUrl,
376
+ expandOnReceive = true,
377
+ headers: customHeaders = {},
378
+ fetch: customFetch = globalThis.fetch
379
+ } = config;
380
+ const defaultHeaders = {
381
+ "Accept-Terse": "true",
382
+ "Content-Type": "application/json",
383
+ ...customHeaders
384
+ };
385
+ return {
386
+ /**
387
+ * GET request - receives TerseJSON and returns Proxy-wrapped (or raw)
388
+ */
389
+ async get(path, options) {
390
+ const response = await customFetch(`${baseUrl}${path}`, {
391
+ method: "GET",
392
+ headers: { ...defaultHeaders, ...options?.headers }
393
+ });
394
+ if (!response.ok) {
395
+ throw new Error(`TerseServiceClient: GET ${path} failed with status ${response.status}`);
396
+ }
397
+ const data = await response.json();
398
+ if (isTersePayload(data) && expandOnReceive) {
399
+ return wrapWithProxy(data);
400
+ }
401
+ return data;
402
+ },
403
+ /**
404
+ * POST request with automatic compression
405
+ */
406
+ async post(path, data, options) {
407
+ const shouldCompress = options?.compress ?? Array.isArray(data);
408
+ const body = shouldCompress && Array.isArray(data) ? JSON.stringify(compress(data)) : JSON.stringify(data);
409
+ const headers = {
410
+ ...defaultHeaders,
411
+ ...options?.headers,
412
+ ...shouldCompress ? { "X-Terse-JSON": "true" } : {}
413
+ };
414
+ const response = await customFetch(`${baseUrl}${path}`, {
415
+ method: "POST",
416
+ headers,
417
+ body
418
+ });
419
+ if (!response.ok) {
420
+ throw new Error(`TerseServiceClient: POST ${path} failed with status ${response.status}`);
421
+ }
422
+ const responseData = await response.json();
423
+ if (isTersePayload(responseData) && expandOnReceive) {
424
+ return wrapWithProxy(responseData);
425
+ }
426
+ return responseData;
427
+ },
428
+ /**
429
+ * Forward a raw TersePayload to another endpoint without expansion.
430
+ * Useful for passing data between services without intermediate deserialization.
431
+ */
432
+ async forward(path, payload, options) {
433
+ const response = await customFetch(`${baseUrl}${path}`, {
434
+ method: "POST",
435
+ headers: {
436
+ ...defaultHeaders,
437
+ ...options?.headers,
438
+ "X-Terse-JSON": "true"
439
+ },
440
+ body: JSON.stringify(payload)
441
+ });
442
+ if (!response.ok) {
443
+ throw new Error(`TerseServiceClient: forward to ${path} failed with status ${response.status}`);
444
+ }
445
+ }
446
+ };
447
+ }
448
+
449
+ export { TerseCache, compressStream, createTerseServiceClient };
450
+ //# sourceMappingURL=server-memory.mjs.map
451
+ //# sourceMappingURL=server-memory.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/core.ts","../src/server-memory.ts"],"names":[],"mappings":";AAiKO,SAAS,eAAe,KAAA,EAAuC;AACpE,EAAA,OACE,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,QACV,WAAA,IAAe,KAAA,IACd,KAAA,CAAuB,SAAA,KAAc,IAAA,IACtC,GAAA,IAAO,KAAA,IACP,GAAA,IAAO,SACP,GAAA,IAAO,KAAA;AAEX;;;ACrJA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,GAAA,GAAM,EAAA;AACV,EAAA,IAAI,SAAA,GAAY,KAAA;AAEhB,EAAA,GAAG;AACD,IAAA,GAAA,GAAM,MAAA,CAAO,YAAA,CAAa,EAAA,GAAM,SAAA,GAAY,EAAG,CAAA,GAAI,GAAA;AACnD,IAAA,SAAA,GAAY,IAAA,CAAK,KAAA,CAAM,SAAA,GAAY,EAAE,CAAA,GAAI,CAAA;AAAA,EAC3C,SAAS,SAAA,IAAa,CAAA;AAEtB,EAAA,OAAO,GAAA;AACT;AAKA,SAAS,iBAAiB,KAAA,EAAuB;AAC/C,EAAA,OAAO,OAAO,KAAK,CAAA;AACrB;AAKA,SAAS,sBAAsB,KAAA,EAAuB;AACpD,EAAA,MAAM,WAAA,GAAc,IAAA,CAAK,KAAA,CAAM,KAAA,GAAQ,CAAC,CAAA;AACxC,EAAA,MAAM,QAAA,GAAY,QAAQ,CAAA,GAAK,CAAA;AAC/B,EAAA,OAAO,cAAA,CAAe,WAAW,CAAA,GAAI,QAAA;AACvC;AAMA,SAAS,eAAe,KAAA,EAAuB;AAC7C,EAAA,IAAI,KAAA,KAAU,GAAG,OAAO,GAAA;AACxB,EAAA,OAAO,cAAA,CAAe,QAAQ,CAAC,CAAA;AACjC;AAKA,SAAS,iBAAA,CAAkB,MAAA,EAAgB,KAAA,GAA6B,SAAA,EAAyB;AAC/F,EAAA,OAAO,CAAC,KAAA,KAAkB;AACxB,IAAA,IAAI,UAAU,OAAA,EAAS;AACrB,MAAA,OAAO,MAAA,GAAS,eAAe,KAAK,CAAA;AAAA,IACtC;AACA,IAAA,OAAO,MAAA,GAAS,KAAA;AAAA,EAClB,CAAA;AACF;AAKO,SAAS,mBAAmB,OAAA,EAAgE;AAEjG,EAAA,IAAI,OAAO,YAAY,UAAA,EAAY;AACjC,IAAA,OAAO,EAAE,SAAA,EAAW,OAAA,EAAS,IAAA,EAAM,QAAA,EAAS;AAAA,EAC9C;AAGA,EAAA,IAAI,OAAO,YAAY,QAAA,EAAU;AAC/B,IAAA,QAAQ,OAAA;AAAS,MACf,KAAK,OAAA;AACH,QAAA,OAAO,EAAE,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAM,OAAA,EAAQ;AAAA,MACpD,KAAK,SAAA;AACH,QAAA,OAAO,EAAE,SAAA,EAAW,gBAAA,EAAkB,IAAA,EAAM,SAAA,EAAU;AAAA,MACxD,KAAK,cAAA;AACH,QAAA,OAAO,EAAE,SAAA,EAAW,qBAAA,EAAuB,IAAA,EAAM,cAAA,EAAe;AAAA,MAClE,KAAK,OAAA;AACH,QAAA,OAAO,EAAE,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAM,OAAA,EAAQ;AAAA,MACpD,KAAK,UAAA;AACH,QAAA,OAAO,EAAE,SAAA,EAAW,iBAAA,CAAkB,GAAG,CAAA,EAAG,MAAM,YAAA,EAAa;AAAA,MACjE;AACE,QAAA,OAAO,EAAE,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAM,OAAA,EAAQ;AAAA;AACtD,EACF;AAGA,EAAA,IAAI,OAAO,OAAA,KAAY,QAAA,IAAY,QAAA,IAAY,OAAA,EAAS;AACtD,IAAA,OAAO;AAAA,MACL,WAAW,iBAAA,CAAkB,OAAA,CAAQ,MAAA,EAAQ,OAAA,CAAQ,SAAS,SAAS,CAAA;AAAA,MACvE,IAAA,EAAM,CAAA,SAAA,EAAY,OAAA,CAAQ,MAAM,CAAA;AAAA,KAClC;AAAA,EACF;AAEA,EAAA,OAAO,EAAE,SAAA,EAAW,cAAA,EAAgB,IAAA,EAAM,OAAA,EAAQ;AACpD;AAKA,SAAS,kBAAA,CAAmB,UAAsC,QAAA,EAA0B;AAC1F,EAAA,IAAI,QAAA,KAAa,MAAA,IAAa,QAAA,KAAa,MAAA,EAAQ;AACjD,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,aAAa,SAAA,EAAW;AAC1B,IAAA,OAAO,CAAA;AAAA,EACT;AACA,EAAA,IAAI,aAAa,QAAA,EAAU;AACzB,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,IAAI,OAAO,aAAa,QAAA,EAAU;AAChC,IAAA,OAAO,QAAA;AAAA,EACT;AACA,EAAA,OAAO,QAAA;AACT;AAgBA,SAAS,WAAA,CACP,IAAA,EACA,OAAA,EACA,YAAA,GAAuB,CAAA,EACV;AACb,EAAA,MAAM,IAAA,uBAAW,GAAA,EAAY;AAC7B,EAAA,MAAM,EAAE,YAAA,EAAc,QAAA,EAAU,cAAA,EAAgB,WAAA,EAAa,aAAY,GAAI,OAAA;AAE7E,EAAA,IAAI,YAAA,IAAgB,UAAU,OAAO,IAAA;AAGrC,EAAA,MAAM,SAAA,uBAAgB,GAAA,EAAoB;AAE1C,EAAA,KAAA,MAAW,QAAQ,IAAA,EAAM;AACvB,IAAA,IAAI,OAAO,IAAA,KAAS,QAAA,IAAY,IAAA,KAAS,IAAA,EAAM;AAE/C,IAAA,KAAA,MAAW,GAAA,IAAO,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,EAAG;AAEnC,MAAA,IAAI,WAAA,EAAa,QAAA,CAAS,GAAG,CAAA,EAAG;AAGhC,MAAA,MAAM,gBAAgB,WAAA,EAAa,QAAA,CAAS,GAAG,CAAA,IAAK,IAAI,MAAA,IAAU,YAAA;AAElE,MAAA,IAAI,aAAA,EAAe;AACjB,QAAA,IAAA,CAAK,IAAI,GAAG,CAAA;AACZ,QAAA,SAAA,CAAU,IAAI,GAAA,EAAA,CAAM,SAAA,CAAU,IAAI,GAAG,CAAA,IAAK,KAAK,CAAC,CAAA;AAAA,MAClD;AAGA,MAAA,MAAM,KAAA,GAAQ,KAAK,GAAG,CAAA;AAEtB,MAAA,IAAI,mBAAmB,SAAA,EAAW;AAEhC,QAAA;AAAA,MACF;AAEA,MAAA,IAAI,KAAA,CAAM,QAAQ,KAAK,CAAA,IAAK,MAAM,MAAA,GAAS,CAAA,IAAK,mBAAA,CAAoB,KAAK,CAAA,EAAG;AAE1E,QAAA,MAAM,UAAA,GAAa,WAAA;AAAA,UACjB,KAAA;AAAA,UACA,OAAA;AAAA,UACA,YAAA,GAAe;AAAA,SACjB;AACA,QAAA,UAAA,CAAW,OAAA,CAAQ,CAAA,CAAA,KAAK,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAAA,MACrC,CAAA,MAAA,IACE,cAAA,KAAmB,QAAA,IACnB,OAAO,KAAA,KAAU,QAAA,IACjB,KAAA,KAAU,IAAA,IACV,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EACpB;AAEA,QAAA,MAAM,UAAA,GAAa,WAAA;AAAA,UACjB,CAAC,KAAgC,CAAA;AAAA,UACjC,OAAA;AAAA,UACA,YAAA,GAAe;AAAA,SACjB;AACA,QAAA,UAAA,CAAW,OAAA,CAAQ,CAAA,CAAA,KAAK,IAAA,CAAK,GAAA,CAAI,CAAC,CAAC,CAAA;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAGA,EAAA,IAAI,OAAA,CAAQ,eAAA,IAAmB,IAAA,CAAK,MAAA,GAAS,CAAA,EAAG;AAC9C,IAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,CAAA,IAAK,SAAA,EAAW;AACpC,MAAA,IAAI,KAAA,GAAQ,KAAK,MAAA,EAAQ;AACvB,QAAA,IAAA,CAAK,OAAO,GAAG,CAAA;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,EAAA,OAAO,IAAA;AACT;AAKO,SAAS,oBAAoB,IAAA,EAAkD;AACpF,EAAA,IAAI,CAAC,MAAM,OAAA,CAAQ,IAAI,KAAK,IAAA,CAAK,MAAA,KAAW,GAAG,OAAO,KAAA;AAGtD,EAAA,OAAO,IAAA,CAAK,KAAA;AAAA,IACV,CAAA,IAAA,KAAQ,OAAO,IAAA,KAAS,QAAA,IAAY,SAAS,IAAA,IAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI;AAAA,GAC1E;AACF;AAKA,SAAS,cAAA,CACP,GAAA,EACA,UAAA,EACA,QAAA,EACA,eAAuB,CAAA,EACE;AACzB,EAAA,IAAI,YAAA,IAAgB,UAAU,OAAO,GAAA;AAErC,EAAA,MAAM,aAAsC,EAAC;AAE7C,EAAA,KAAA,MAAW,CAAC,GAAA,EAAK,KAAK,KAAK,MAAA,CAAO,OAAA,CAAQ,GAAG,CAAA,EAAG;AAC9C,IAAA,MAAM,QAAA,GAAW,UAAA,CAAW,GAAA,CAAI,GAAG,CAAA,IAAK,GAAA;AAExC,IAAA,IAAI,MAAM,OAAA,CAAQ,KAAK,CAAA,IAAK,mBAAA,CAAoB,KAAK,CAAA,EAAG;AAEtD,MAAA,UAAA,CAAW,QAAQ,IAAI,KAAA,CAAM,GAAA;AAAA,QAAI,CAAA,IAAA,KAC/B,cAAA;AAAA,UACE,IAAA;AAAA,UACA,UAAA;AAAA,UACA,QAAA;AAAA,UACA,YAAA,GAAe;AAAA;AACjB,OACF;AAAA,IACF,CAAA,MAAA,IAAW,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,QAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AAE/E,MAAA,UAAA,CAAW,QAAQ,CAAA,GAAI,cAAA;AAAA,QACrB,KAAA;AAAA,QACA,UAAA;AAAA,QACA,QAAA;AAAA,QACA,YAAA,GAAe;AAAA,OACjB;AAAA,IACF,CAAA,MAAO;AACL,MAAA,UAAA,CAAW,QAAQ,CAAA,GAAI,KAAA;AAAA,IACzB;AAAA,EACF;AAEA,EAAA,OAAO,UAAA;AACT;AAmDO,SAAS,QAAA,CACd,IAAA,EACA,OAAA,GAA2B,EAAC,EACH;AACzB,EAAA,MAAM;AAAA,IACJ,YAAA,GAAe,CAAA;AAAA,IACf,QAAA,GAAW,EAAA;AAAA,IACX,UAAA,GAAa,OAAA;AAAA,IACb,cAAA,GAAiB,MAAA;AAAA,IACjB,eAAA,GAAkB,KAAA;AAAA,IAClB,WAAA;AAAA,IACA;AAAA,GACF,GAAI,OAAA;AAGJ,EAAA,MAAM,EAAE,SAAA,EAAW,IAAA,EAAM,WAAA,EAAY,GAAI,mBAAmB,UAAU,CAAA;AAGtE,EAAA,MAAM,cAAA,GAAiB,kBAAA,CAAmB,cAAA,EAAgB,QAAQ,CAAA;AAGlE,EAAA,MAAM,OAAA,GAAU,YAAY,IAAA,EAAM;AAAA,IAChC,YAAA;AAAA,IACA,QAAA,EAAU,cAAA;AAAA,IACV,cAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,GACD,CAAA;AAID,EAAA,MAAM,UAAA,GAAa,KAAA,CAAM,IAAA,CAAK,OAAO,EAAE,IAAA,EAAK;AAG5C,EAAA,MAAM,UAAA,uBAAiB,GAAA,EAAoB;AAC3C,EAAA,MAAM,SAAiC,EAAC;AAExC,EAAA,UAAA,CAAW,OAAA,CAAQ,CAAC,GAAA,EAAK,KAAA,KAAU;AACjC,IAAA,MAAM,QAAA,GAAW,UAAU,KAAK,CAAA;AAEhC,IAAA,IAAI,QAAA,CAAS,MAAA,GAAS,GAAA,CAAI,MAAA,EAAQ;AAChC,MAAA,UAAA,CAAW,GAAA,CAAI,KAAK,QAAQ,CAAA;AAC5B,MAAA,MAAA,CAAO,QAAQ,CAAA,GAAI,GAAA;AAAA,IACrB;AAAA,EACF,CAAC,CAAA;AAGD,EAAA,MAAM,aAAa,IAAA,CAAK,GAAA;AAAA,IAAI,CAAA,IAAA,KAC1B,cAAA,CAAe,IAAA,EAAM,UAAA,EAAY,cAAc;AAAA,GACjD;AAEA,EAAA,OAAO;AAAA,IACL,SAAA,EAAW,IAAA;AAAA,IACX,CAAA,EAAG,CAAA;AAAA,IACH,CAAA,EAAG,MAAA;AAAA,IACH,CAAA,EAAG,UAAA;AAAA,IACH,CAAA,EAAG;AAAA,GACL;AACF;AA8BO,SAAS,gBAAA,CACd,YACA,MAAA,EACG;AAEH,EAAA,MAAM,kBAAkB,IAAI,GAAA;AAAA,IAC1B,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,GAAA,CAAI,CAAC,CAAC,KAAA,EAAO,QAAQ,CAAA,KAAM,CAAC,QAAA,EAAU,KAAK,CAAC;AAAA,GACrE;AAEA,EAAA,MAAM,OAAA,GAAiD;AAAA,IACrD,GAAA,CAAI,QAAQ,IAAA,EAAuB;AACjC,MAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAA;AAAA,MACjC;AAGA,MAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,GAAA,CAAI,IAAI,CAAA;AACzC,MAAA,MAAM,YAAY,QAAA,IAAY,IAAA;AAC9B,MAAA,MAAM,KAAA,GAAQ,OAAO,SAAS,CAAA;AAG9B,MAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,KAAK,CAAA,EAAG;AACxB,QAAA,OAAO,KAAA,CAAM,IAAI,CAAA,IAAA,KAAQ;AACvB,UAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,KAAS,QAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrE,YAAA,OAAO,gBAAA,CAAiB,MAAiC,MAAM,CAAA;AAAA,UACjE;AACA,UAAA,OAAO,IAAA;AAAA,QACT,CAAC,CAAA;AAAA,MACH;AAEA,MAAA,IAAI,OAAO,KAAA,KAAU,QAAA,IAAY,KAAA,KAAU,IAAA,EAAM;AAC/C,QAAA,OAAO,gBAAA,CAAiB,OAAkC,MAAM,CAAA;AAAA,MAClE;AAEA,MAAA,OAAO,KAAA;AAAA,IACT,CAAA;AAAA,IAEA,GAAA,CAAI,QAAQ,IAAA,EAAuB;AACjC,MAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAO,OAAA,CAAQ,GAAA,CAAI,MAAA,EAAQ,IAAI,CAAA;AAAA,MACjC;AACA,MAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,GAAA,CAAI,IAAI,CAAA;AACzC,MAAA,OAAA,CAAQ,YAAY,IAAA,KAAS,MAAA;AAAA,IAC/B,CAAA;AAAA,IAEA,QAAQ,MAAA,EAAQ;AAEd,MAAA,OAAO,MAAA,CAAO,KAAK,MAAM,CAAA,CAAE,IAAI,CAAA,QAAA,KAAY,MAAA,CAAO,QAAQ,CAAA,IAAK,QAAQ,CAAA;AAAA,IACzE,CAAA;AAAA,IAEA,wBAAA,CAAyB,QAAQ,IAAA,EAAuB;AACtD,MAAA,IAAI,OAAO,SAAS,QAAA,EAAU;AAC5B,QAAA,OAAO,OAAA,CAAQ,wBAAA,CAAyB,MAAA,EAAQ,IAAI,CAAA;AAAA,MACtD;AACA,MAAA,MAAM,QAAA,GAAW,eAAA,CAAgB,GAAA,CAAI,IAAI,CAAA;AACzC,MAAA,MAAM,YAAY,QAAA,IAAY,IAAA;AAC9B,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,wBAAA,CAAyB,MAAA,EAAQ,SAAS,CAAA;AACpE,MAAA,IAAI,UAAA,EAAY;AACd,QAAA,OAAO,EAAE,GAAG,UAAA,EAAY,UAAA,EAAY,IAAA,EAAM,cAAc,IAAA,EAAK;AAAA,MAC/D;AACA,MAAA,OAAO,MAAA;AAAA,IACT;AAAA,GACF;AAEA,EAAA,OAAO,IAAI,KAAA,CAAM,UAAA,EAAY,OAAO,CAAA;AACtC;AAKO,SAAS,cAAiB,OAAA,EAA0B;AACzD,EAAA,IAAI,KAAA,CAAM,OAAA,CAAQ,OAAA,CAAQ,CAAC,CAAA,EAAG;AAC5B,IAAA,OAAO,OAAA,CAAQ,CAAA,CAAE,GAAA,CAAI,CAAA,IAAA,KAAQ;AAC3B,MAAA,IAAI,OAAO,SAAS,QAAA,IAAY,IAAA,KAAS,QAAQ,CAAC,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,EAAG;AACrE,QAAA,OAAO,gBAAA,CAAiB,IAAA,EAAiC,OAAA,CAAQ,CAAC,CAAA;AAAA,MACpE;AACA,MAAA,OAAO,IAAA;AAAA,IACT,CAAC,CAAA;AAAA,EACH;AAEA,EAAA,IAAI,OAAO,OAAA,CAAQ,CAAA,KAAM,QAAA,IAAY,OAAA,CAAQ,MAAM,IAAA,EAAM;AACvD,IAAA,OAAO,gBAAA,CAAiB,OAAA,CAAQ,CAAA,EAA8B,OAAA,CAAQ,CAAC,CAAA;AAAA,EACzE;AAEA,EAAA,OAAO,OAAA,CAAQ,CAAA;AACjB;;;AClcO,IAAM,aAAN,MAAkF;AAAA,EAKvF,WAAA,CAAY,OAAA,GAA6B,EAAC,EAAG;AAF7C,IAAA,IAAA,CAAQ,cAAwB,EAAC;AAG/B,IAAA,IAAA,CAAK,KAAA,uBAAY,GAAA,EAAI;AACrB,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AAAA,EACjB;AAAA;AAAA;AAAA;AAAA,EAKA,GAAA,CAAI,GAAA,EAAa,IAAA,EAAS,GAAA,EAAoB;AAE5C,IAAA,IAAI,IAAA,CAAK,QAAQ,OAAA,IAAW,IAAA,CAAK,MAAM,IAAA,IAAQ,IAAA,CAAK,QAAQ,OAAA,EAAS;AACnE,MAAA,MAAM,SAAA,GAAY,IAAA,CAAK,WAAA,CAAY,KAAA,EAAM;AACzC,MAAA,IAAI,SAAA,EAAW;AACb,QAAA,IAAA,CAAK,KAAA,CAAM,OAAO,SAAS,CAAA;AAAA,MAC7B;AAAA,IACF;AAEA,IAAA,MAAM,OAAA,GAAU,QAAA,CAAS,IAAA,EAAM,IAAA,CAAK,QAAQ,eAAe,CAAA;AAC3D,IAAA,MAAM,YAAA,GAAe,GAAA,IAAO,IAAA,CAAK,OAAA,CAAQ,UAAA;AACzC,IAAA,MAAM,SAAA,GAAY,YAAA,GAAe,IAAA,CAAK,GAAA,KAAQ,YAAA,GAAe,MAAA;AAE7D,IAAA,IAAA,CAAK,MAAM,GAAA,CAAI,GAAA,EAAK,EAAE,OAAA,EAAS,WAAW,CAAA;AAC1C,IAAA,IAAA,CAAK,kBAAkB,GAAG,CAAA;AAAA,EAC5B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,IAAI,GAAA,EAA4B;AAC9B,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAGnB,IAAA,IAAI,MAAM,SAAA,IAAa,IAAA,CAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AACnD,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,IAAA,CAAK,sBAAsB,GAAG,CAAA;AAC9B,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,IAAA,CAAK,kBAAkB,GAAG,CAAA;AAC1B,IAAA,OAAO,aAAA,CAAiB,MAAM,OAAO,CAAA;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,GAAA,EAA0C;AAC/C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,MAAA;AAEnB,IAAA,IAAI,MAAM,SAAA,IAAa,IAAA,CAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AACnD,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,IAAA,CAAK,sBAAsB,GAAG,CAAA;AAC9B,MAAA,OAAO,MAAA;AAAA,IACT;AAEA,IAAA,OAAO,KAAA,CAAM,OAAA;AAAA,EACf;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,GAAA,EAAsB;AACxB,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,GAAG,CAAA;AAChC,IAAA,IAAI,CAAC,OAAO,OAAO,KAAA;AAEnB,IAAA,IAAI,MAAM,SAAA,IAAa,IAAA,CAAK,GAAA,EAAI,GAAI,MAAM,SAAA,EAAW;AACnD,MAAA,IAAA,CAAK,KAAA,CAAM,OAAO,GAAG,CAAA;AACrB,MAAA,IAAA,CAAK,sBAAsB,GAAG,CAAA;AAC9B,MAAA,OAAO,KAAA;AAAA,IACT;AAEA,IAAA,OAAO,IAAA;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,GAAA,EAAsB;AAC3B,IAAA,IAAA,CAAK,sBAAsB,GAAG,CAAA;AAC9B,IAAA,OAAO,IAAA,CAAK,KAAA,CAAM,MAAA,CAAO,GAAG,CAAA;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,KAAA,GAAc;AACZ,IAAA,IAAA,CAAK,MAAM,KAAA,EAAM;AACjB,IAAA,IAAA,CAAK,cAAc,EAAC;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,IAAA,GAAe;AACjB,IAAA,OAAO,KAAK,KAAA,CAAM,IAAA;AAAA,EACpB;AAAA,EAEQ,kBAAkB,GAAA,EAAmB;AAC3C,IAAA,IAAA,CAAK,sBAAsB,GAAG,CAAA;AAC9B,IAAA,IAAA,CAAK,WAAA,CAAY,KAAK,GAAG,CAAA;AAAA,EAC3B;AAAA,EAEQ,sBAAsB,GAAA,EAAmB;AAC/C,IAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,GAAG,CAAA;AAC1C,IAAA,IAAI,QAAQ,EAAA,EAAI;AACd,MAAA,IAAA,CAAK,WAAA,CAAY,MAAA,CAAO,KAAA,EAAO,CAAC,CAAA;AAAA,IAClC;AAAA,EACF;AACF;AA+BA,gBAAuB,cAAA,CACrB,MAAA,EACA,OAAA,GAAiC,EAAC,EACgB;AAClD,EAAA,MAAM,SAAA,GAAY,QAAQ,SAAA,IAAa,GAAA;AACvC,EAAA,MAAM,EAAE,SAAA,EAAW,CAAA,EAAG,GAAG,iBAAgB,GAAI,OAAA;AAE7C,EAAA,IAAI,QAAa,EAAC;AAElB,EAAA,WAAA,MAAiB,QAAQ,MAAA,EAAQ;AAC/B,IAAA,KAAA,CAAM,KAAK,IAAI,CAAA;AAEf,IAAA,IAAI,KAAA,CAAM,UAAU,SAAA,EAAW;AAC7B,MAAA,MAAM,QAAA,CAAS,OAAO,eAAe,CAAA;AACrC,MAAA,KAAA,GAAQ,EAAC;AAAA,IACX;AAAA,EACF;AAGA,EAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,IAAA,MAAM,QAAA,CAAS,OAAO,eAAe,CAAA;AAAA,EACvC;AACF;AAuCO,SAAS,yBAAyB,MAAA,EAAkC;AACzE,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,eAAA,GAAkB,IAAA;AAAA,IAClB,OAAA,EAAS,gBAAgB,EAAC;AAAA,IAC1B,KAAA,EAAO,cAAc,UAAA,CAAW;AAAA,GAClC,GAAI,MAAA;AAEJ,EAAA,MAAM,cAAA,GAAiB;AAAA,IACrB,cAAA,EAAgB,MAAA;AAAA,IAChB,cAAA,EAAgB,kBAAA;AAAA,IAChB,GAAG;AAAA,GACL;AAEA,EAAA,OAAO;AAAA;AAAA;AAAA;AAAA,IAIL,MAAM,GAAA,CAAO,IAAA,EAAc,OAAA,EAA4D;AACrF,MAAA,MAAM,WAAW,MAAM,WAAA,CAAY,GAAG,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,QACtD,MAAA,EAAQ,KAAA;AAAA,QACR,SAAS,EAAE,GAAG,cAAA,EAAgB,GAAG,SAAS,OAAA;AAAQ,OACnD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,wBAAA,EAA2B,IAAI,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MACzF;AAEA,MAAA,MAAM,IAAA,GAAO,MAAM,QAAA,CAAS,IAAA,EAAK;AAEjC,MAAA,IAAI,cAAA,CAAe,IAAI,CAAA,IAAK,eAAA,EAAiB;AAC3C,QAAA,OAAO,cAAiB,IAAqD,CAAA;AAAA,MAC/E;AAEA,MAAA,OAAO,IAAA;AAAA,IACT,CAAA;AAAA;AAAA;AAAA;AAAA,IAKA,MAAM,IAAA,CACJ,IAAA,EACA,IAAA,EACA,OAAA,EACY;AACZ,MAAA,MAAM,cAAA,GAAiB,OAAA,EAAS,QAAA,IAAY,KAAA,CAAM,QAAQ,IAAI,CAAA;AAC9D,MAAA,MAAM,IAAA,GAAO,cAAA,IAAkB,KAAA,CAAM,OAAA,CAAQ,IAAI,CAAA,GAC7C,IAAA,CAAK,SAAA,CAAU,QAAA,CAAS,IAAiC,CAAC,CAAA,GAC1D,IAAA,CAAK,UAAU,IAAI,CAAA;AAEvB,MAAA,MAAM,OAAA,GAAU;AAAA,QACd,GAAG,cAAA;AAAA,QACH,GAAG,OAAA,EAAS,OAAA;AAAA,QACZ,GAAI,cAAA,GAAiB,EAAE,cAAA,EAAgB,MAAA,KAAW;AAAC,OACrD;AAEA,MAAA,MAAM,WAAW,MAAM,WAAA,CAAY,GAAG,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,QACtD,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA;AAAA,QACA;AAAA,OACD,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,yBAAA,EAA4B,IAAI,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MAC1F;AAEA,MAAA,MAAM,YAAA,GAAe,MAAM,QAAA,CAAS,IAAA,EAAK;AAEzC,MAAA,IAAI,cAAA,CAAe,YAAY,CAAA,IAAK,eAAA,EAAiB;AACnD,QAAA,OAAO,cAAiB,YAA6D,CAAA;AAAA,MACvF;AAEA,MAAA,OAAO,YAAA;AAAA,IACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMA,MAAM,OAAA,CACJ,IAAA,EACA,OAAA,EACA,OAAA,EACe;AACf,MAAA,MAAM,WAAW,MAAM,WAAA,CAAY,GAAG,OAAO,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI;AAAA,QACtD,MAAA,EAAQ,MAAA;AAAA,QACR,OAAA,EAAS;AAAA,UACP,GAAG,cAAA;AAAA,UACH,GAAG,OAAA,EAAS,OAAA;AAAA,UACZ,cAAA,EAAgB;AAAA,SAClB;AAAA,QACA,IAAA,EAAM,IAAA,CAAK,SAAA,CAAU,OAAO;AAAA,OAC7B,CAAA;AAED,MAAA,IAAI,CAAC,SAAS,EAAA,EAAI;AAChB,QAAA,MAAM,IAAI,KAAA,CAAM,CAAA,+BAAA,EAAkC,IAAI,CAAA,oBAAA,EAAuB,QAAA,CAAS,MAAM,CAAA,CAAE,CAAA;AAAA,MAChG;AAAA,IACF;AAAA,GACF;AACF","file":"server-memory.mjs","sourcesContent":["/**\n * TerseJSON Types\n *\n * Defines the wire format and configuration options for transparent\n * JSON key compression.\n */\n\n/**\n * The compressed format sent over the wire\n */\nexport interface TersePayload<T = unknown> {\n /** Marker to identify this as a TerseJSON payload */\n __terse__: true;\n /** Version for future compatibility */\n v: 1;\n /** Key mapping: short key -> original key */\n k: Record<string, string>;\n /** The compressed data with short keys */\n d: T;\n /** Pattern used for key generation (for debugging/info) */\n p?: string;\n}\n\n/**\n * Built-in key pattern presets\n */\nexport type KeyPatternPreset =\n | 'alpha' // a, b, c, ... z, aa, ab (default)\n | 'numeric' // 0, 1, 2, ... 9, 10, 11\n | 'alphanumeric' // a1, a2, ... a9, b1, b2\n | 'short' // _, __, ___, a, b (shortest possible)\n | 'prefixed'; // k0, k1, k2 (with 'k' prefix)\n\n/**\n * Custom key generator function\n */\nexport type KeyGenerator = (index: number) => string;\n\n/**\n * Key pattern configuration\n */\nexport type KeyPattern =\n | KeyPatternPreset\n | { prefix: string; style?: 'numeric' | 'alpha' }\n | KeyGenerator;\n\n/**\n * How to handle nested structures\n */\nexport type NestedHandling =\n | 'deep' // Compress all nested objects/arrays (default)\n | 'shallow' // Only compress top-level array\n | 'arrays' // Only compress nested arrays, not single objects\n | number; // Specific depth limit (1 = shallow, Infinity = deep)\n\n/**\n * Compression options\n */\nexport interface CompressOptions {\n /**\n * Minimum key length to consider for compression\n * Keys shorter than this won't be shortened\n * @default 3\n */\n minKeyLength?: number;\n\n /**\n * Maximum depth to traverse for nested objects\n * @default 10\n */\n maxDepth?: number;\n\n /**\n * Key pattern to use for generating short keys\n * @default 'alpha'\n */\n keyPattern?: KeyPattern;\n\n /**\n * How to handle nested objects and arrays\n * @default 'deep'\n */\n nestedHandling?: NestedHandling;\n\n /**\n * Only compress keys that appear in all objects (homogeneous)\n * @default false\n */\n homogeneousOnly?: boolean;\n\n /**\n * Keys to always exclude from compression\n */\n excludeKeys?: string[];\n\n /**\n * Keys to always include in compression (even if short)\n */\n includeKeys?: string[];\n}\n\n/**\n * Configuration options for the Express middleware\n */\nexport interface TerseMiddlewareOptions extends CompressOptions {\n /**\n * Minimum array length to trigger compression\n * @default 2\n */\n minArrayLength?: number;\n\n /**\n * Custom function to determine if a response should be compressed\n * Return false to skip compression for specific responses\n */\n shouldCompress?: (data: unknown, req: unknown) => boolean;\n\n /**\n * Custom header name for signaling terse responses\n * @default 'x-terse-json'\n */\n headerName?: string;\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n}\n\n/**\n * Configuration options for the client\n */\nexport interface TerseClientOptions {\n /**\n * Custom header name to check for terse responses\n * @default 'x-terse-json'\n */\n headerName?: string;\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n\n /**\n * Automatically expand terse responses\n * @default true\n */\n autoExpand?: boolean;\n}\n\n/**\n * Type helper to preserve original types through compression\n */\nexport type Tersed<T> = T;\n\n/**\n * Check if a value is a TersePayload\n */\nexport function isTersePayload(value: unknown): value is TersePayload {\n return (\n typeof value === 'object' &&\n value !== null &&\n '__terse__' in value &&\n (value as TersePayload).__terse__ === true &&\n 'v' in value &&\n 'k' in value &&\n 'd' in value\n );\n}\n\n/**\n * GraphQL terse metadata (attached to response)\n */\nexport interface GraphQLTerseMetadata {\n /** Version for compatibility */\n v: 1;\n /** Key mapping: short key -> original key */\n k: Record<string, string>;\n /** JSON paths to compressed arrays (e.g., [\"data.users\", \"data.products\"]) */\n paths: string[];\n}\n\n/**\n * GraphQL response with terse compression\n */\nexport interface GraphQLTerseResponse<T = unknown> {\n /** The compressed data with short keys in arrays */\n data: T;\n /** GraphQL errors (untouched) */\n errors?: Array<{ message: string; [key: string]: unknown }>;\n /** GraphQL extensions (untouched) */\n extensions?: Record<string, unknown>;\n /** Terse metadata */\n __terse__: GraphQLTerseMetadata;\n}\n\n/**\n * GraphQL middleware options\n */\nexport interface GraphQLTerseOptions extends CompressOptions {\n /**\n * Minimum array length to trigger compression\n * @default 2\n */\n minArrayLength?: number;\n\n /**\n * Custom function to determine if a path should be compressed\n * Return false to skip compression for specific paths\n */\n shouldCompress?: (data: unknown, path: string) => boolean;\n\n /**\n * Paths to exclude from compression (e.g., [\"data.config\"])\n */\n excludePaths?: string[];\n\n /**\n * Enable debug logging\n * @default false\n */\n debug?: boolean;\n}\n\n/**\n * Check if a value is a GraphQL terse response\n */\nexport function isGraphQLTersePayload(value: unknown): value is GraphQLTerseResponse {\n return (\n typeof value === 'object' &&\n value !== null &&\n 'data' in value &&\n '__terse__' in value &&\n typeof (value as GraphQLTerseResponse).__terse__ === 'object' &&\n (value as GraphQLTerseResponse).__terse__ !== null &&\n 'v' in (value as GraphQLTerseResponse).__terse__ &&\n 'k' in (value as GraphQLTerseResponse).__terse__ &&\n 'paths' in (value as GraphQLTerseResponse).__terse__\n );\n}\n","/**\n * TerseJSON Core\n *\n * The core compression and expansion algorithms.\n */\n\nimport {\n TersePayload,\n isTersePayload,\n KeyPattern,\n KeyGenerator,\n NestedHandling,\n CompressOptions,\n} from './types';\n\n// ============================================================================\n// KEY PATTERN GENERATORS\n// ============================================================================\n\n/**\n * Alpha pattern: a, b, c, ... z, aa, ab, ...\n */\nfunction alphaGenerator(index: number): string {\n let key = '';\n let remaining = index;\n\n do {\n key = String.fromCharCode(97 + (remaining % 26)) + key;\n remaining = Math.floor(remaining / 26) - 1;\n } while (remaining >= 0);\n\n return key;\n}\n\n/**\n * Numeric pattern: 0, 1, 2, ... 9, 10, 11, ...\n */\nfunction numericGenerator(index: number): string {\n return String(index);\n}\n\n/**\n * Alphanumeric pattern: a1, a2, ... a9, b1, b2, ...\n */\nfunction alphanumericGenerator(index: number): string {\n const letterIndex = Math.floor(index / 9);\n const numIndex = (index % 9) + 1;\n return alphaGenerator(letterIndex) + numIndex;\n}\n\n/**\n * Short pattern: uses shortest possible keys\n * _, a, b, ..., z, aa, ab, ...\n */\nfunction shortGenerator(index: number): string {\n if (index === 0) return '_';\n return alphaGenerator(index - 1);\n}\n\n/**\n * Prefixed pattern: k0, k1, k2, ...\n */\nfunction prefixedGenerator(prefix: string, style: 'numeric' | 'alpha' = 'numeric'): KeyGenerator {\n return (index: number) => {\n if (style === 'alpha') {\n return prefix + alphaGenerator(index);\n }\n return prefix + index;\n };\n}\n\n/**\n * Creates a key generator from a pattern configuration\n */\nexport function createKeyGenerator(pattern: KeyPattern): { generator: KeyGenerator; name: string } {\n // If it's already a function, use it directly\n if (typeof pattern === 'function') {\n return { generator: pattern, name: 'custom' };\n }\n\n // If it's a preset string\n if (typeof pattern === 'string') {\n switch (pattern) {\n case 'alpha':\n return { generator: alphaGenerator, name: 'alpha' };\n case 'numeric':\n return { generator: numericGenerator, name: 'numeric' };\n case 'alphanumeric':\n return { generator: alphanumericGenerator, name: 'alphanumeric' };\n case 'short':\n return { generator: shortGenerator, name: 'short' };\n case 'prefixed':\n return { generator: prefixedGenerator('k'), name: 'prefixed:k' };\n default:\n return { generator: alphaGenerator, name: 'alpha' };\n }\n }\n\n // If it's a prefix config object\n if (typeof pattern === 'object' && 'prefix' in pattern) {\n return {\n generator: prefixedGenerator(pattern.prefix, pattern.style || 'numeric'),\n name: `prefixed:${pattern.prefix}`,\n };\n }\n\n return { generator: alphaGenerator, name: 'alpha' };\n}\n\n/**\n * Resolves nested handling to a numeric depth\n */\nfunction resolveNestedDepth(handling: NestedHandling | undefined, maxDepth: number): number {\n if (handling === undefined || handling === 'deep') {\n return maxDepth;\n }\n if (handling === 'shallow') {\n return 1;\n }\n if (handling === 'arrays') {\n return maxDepth; // Special handling in collect/compress functions\n }\n if (typeof handling === 'number') {\n return handling;\n }\n return maxDepth;\n}\n\n// Legacy generateShortKey removed - use createKeyGenerator instead\n\ninterface CollectKeysOptions {\n minKeyLength: number;\n maxDepth: number;\n nestedHandling: NestedHandling;\n excludeKeys?: string[];\n includeKeys?: string[];\n homogeneousOnly?: boolean;\n}\n\n/**\n * Collects all unique keys from an array of objects\n */\nfunction collectKeys(\n data: Record<string, unknown>[],\n options: CollectKeysOptions,\n currentDepth: number = 0\n): Set<string> {\n const keys = new Set<string>();\n const { minKeyLength, maxDepth, nestedHandling, excludeKeys, includeKeys } = options;\n\n if (currentDepth >= maxDepth) return keys;\n\n // For homogeneous mode, track key counts\n const keyCounts = new Map<string, number>();\n\n for (const item of data) {\n if (typeof item !== 'object' || item === null) continue;\n\n for (const key of Object.keys(item)) {\n // Skip excluded keys\n if (excludeKeys?.includes(key)) continue;\n\n // Include if in includeKeys, or if meets minKeyLength\n const shouldInclude = includeKeys?.includes(key) || key.length >= minKeyLength;\n\n if (shouldInclude) {\n keys.add(key);\n keyCounts.set(key, (keyCounts.get(key) || 0) + 1);\n }\n\n // Handle nested structures based on nestedHandling option\n const value = item[key];\n\n if (nestedHandling === 'shallow') {\n // Don't process nested structures\n continue;\n }\n\n if (Array.isArray(value) && value.length > 0 && isCompressibleArray(value)) {\n // Nested array of objects - always process\n const nestedKeys = collectKeys(\n value as Record<string, unknown>[],\n options,\n currentDepth + 1\n );\n nestedKeys.forEach(k => keys.add(k));\n } else if (\n nestedHandling !== 'arrays' &&\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value)\n ) {\n // Single nested object - skip if nestedHandling is 'arrays'\n const nestedKeys = collectKeys(\n [value as Record<string, unknown>],\n options,\n currentDepth + 1\n );\n nestedKeys.forEach(k => keys.add(k));\n }\n }\n }\n\n // If homogeneous mode, only keep keys that appear in ALL objects\n if (options.homogeneousOnly && data.length > 0) {\n for (const [key, count] of keyCounts) {\n if (count < data.length) {\n keys.delete(key);\n }\n }\n }\n\n return keys;\n}\n\n/**\n * Checks if an array is compressible (array of objects with consistent structure)\n */\nexport function isCompressibleArray(data: unknown): data is Record<string, unknown>[] {\n if (!Array.isArray(data) || data.length === 0) return false;\n\n // Check if all items are objects\n return data.every(\n item => typeof item === 'object' && item !== null && !Array.isArray(item)\n );\n}\n\n/**\n * Compresses an object using the key mapping\n */\nfunction compressObject(\n obj: Record<string, unknown>,\n keyToShort: Map<string, string>,\n maxDepth: number,\n currentDepth: number = 0\n): Record<string, unknown> {\n if (currentDepth >= maxDepth) return obj;\n\n const compressed: Record<string, unknown> = {};\n\n for (const [key, value] of Object.entries(obj)) {\n const shortKey = keyToShort.get(key) ?? key;\n\n if (Array.isArray(value) && isCompressibleArray(value)) {\n // Recursively compress nested arrays\n compressed[shortKey] = value.map(item =>\n compressObject(\n item as Record<string, unknown>,\n keyToShort,\n maxDepth,\n currentDepth + 1\n )\n );\n } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n // Compress single nested objects too\n compressed[shortKey] = compressObject(\n value as Record<string, unknown>,\n keyToShort,\n maxDepth,\n currentDepth + 1\n );\n } else {\n compressed[shortKey] = value;\n }\n }\n\n return compressed;\n}\n\n/**\n * Expands an object using the key mapping\n */\nfunction expandObject(\n obj: Record<string, unknown>,\n shortToKey: Map<string, string>,\n maxDepth: number,\n currentDepth: number = 0\n): Record<string, unknown> {\n if (currentDepth >= maxDepth) return obj;\n\n const expanded: Record<string, unknown> = {};\n\n for (const [shortKey, value] of Object.entries(obj)) {\n const originalKey = shortToKey.get(shortKey) ?? shortKey;\n\n if (Array.isArray(value)) {\n expanded[originalKey] = value.map(item => {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n return expandObject(\n item as Record<string, unknown>,\n shortToKey,\n maxDepth,\n currentDepth + 1\n );\n }\n return item;\n });\n } else if (typeof value === 'object' && value !== null) {\n expanded[originalKey] = expandObject(\n value as Record<string, unknown>,\n shortToKey,\n maxDepth,\n currentDepth + 1\n );\n } else {\n expanded[originalKey] = value;\n }\n }\n\n return expanded;\n}\n\n// Re-export CompressOptions from types for backwards compatibility\nexport type { CompressOptions } from './types';\n\n/**\n * Compresses an array of objects by replacing keys with short aliases\n */\nexport function compress<T extends Record<string, unknown>[]>(\n data: T,\n options: CompressOptions = {}\n): TersePayload<unknown[]> {\n const {\n minKeyLength = 3,\n maxDepth = 10,\n keyPattern = 'alpha',\n nestedHandling = 'deep',\n homogeneousOnly = false,\n excludeKeys,\n includeKeys,\n } = options;\n\n // Create key generator\n const { generator, name: patternName } = createKeyGenerator(keyPattern);\n\n // Resolve nested depth\n const effectiveDepth = resolveNestedDepth(nestedHandling, maxDepth);\n\n // Collect all unique keys\n const allKeys = collectKeys(data, {\n minKeyLength,\n maxDepth: effectiveDepth,\n nestedHandling,\n excludeKeys,\n includeKeys,\n homogeneousOnly,\n });\n\n // Sort keys by frequency of use (most used first) for optimal compression\n // For now, just sort alphabetically for deterministic output\n const sortedKeys = Array.from(allKeys).sort();\n\n // Create bidirectional mapping\n const keyToShort = new Map<string, string>();\n const keyMap: Record<string, string> = {};\n\n sortedKeys.forEach((key, index) => {\n const shortKey = generator(index);\n // Only use short key if it's actually shorter\n if (shortKey.length < key.length) {\n keyToShort.set(key, shortKey);\n keyMap[shortKey] = key;\n }\n });\n\n // Compress the data\n const compressed = data.map(item =>\n compressObject(item, keyToShort, effectiveDepth)\n );\n\n return {\n __terse__: true,\n v: 1,\n k: keyMap,\n d: compressed,\n p: patternName,\n };\n}\n\n/**\n * Expands a TersePayload back to its original form\n */\nexport function expand<T = unknown>(payload: TersePayload): T {\n const shortToKey = new Map(\n Object.entries(payload.k).map(([short, original]) => [short, original])\n );\n\n if (Array.isArray(payload.d)) {\n return payload.d.map(item => {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n return expandObject(item as Record<string, unknown>, shortToKey, 10);\n }\n return item;\n }) as T;\n }\n\n if (typeof payload.d === 'object' && payload.d !== null) {\n return expandObject(payload.d as Record<string, unknown>, shortToKey, 10) as T;\n }\n\n return payload.d as T;\n}\n\n/**\n * Creates a Proxy that transparently maps original keys to short keys\n * This is the magic that makes client-side access seamless\n */\nexport function createTerseProxy<T extends Record<string, unknown>>(\n compressed: Record<string, unknown>,\n keyMap: Record<string, string> // short -> original\n): T {\n // Create reverse map: original -> short\n const originalToShort = new Map(\n Object.entries(keyMap).map(([short, original]) => [original, short])\n );\n\n const handler: ProxyHandler<Record<string, unknown>> = {\n get(target, prop: string | symbol) {\n if (typeof prop === 'symbol') {\n return Reflect.get(target, prop);\n }\n\n // Check if accessing by original key name\n const shortKey = originalToShort.get(prop);\n const actualKey = shortKey ?? prop;\n const value = target[actualKey];\n\n // Recursively proxy nested objects/arrays\n if (Array.isArray(value)) {\n return value.map(item => {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n return createTerseProxy(item as Record<string, unknown>, keyMap);\n }\n return item;\n });\n }\n\n if (typeof value === 'object' && value !== null) {\n return createTerseProxy(value as Record<string, unknown>, keyMap);\n }\n\n return value;\n },\n\n has(target, prop: string | symbol) {\n if (typeof prop === 'symbol') {\n return Reflect.has(target, prop);\n }\n const shortKey = originalToShort.get(prop);\n return (shortKey ?? prop) in target;\n },\n\n ownKeys(target) {\n // Return original key names\n return Object.keys(target).map(shortKey => keyMap[shortKey] ?? shortKey);\n },\n\n getOwnPropertyDescriptor(target, prop: string | symbol) {\n if (typeof prop === 'symbol') {\n return Reflect.getOwnPropertyDescriptor(target, prop);\n }\n const shortKey = originalToShort.get(prop);\n const actualKey = shortKey ?? prop;\n const descriptor = Object.getOwnPropertyDescriptor(target, actualKey);\n if (descriptor) {\n return { ...descriptor, enumerable: true, configurable: true };\n }\n return undefined;\n },\n };\n\n return new Proxy(compressed, handler) as T;\n}\n\n/**\n * Wraps TersePayload data with Proxies for transparent access\n */\nexport function wrapWithProxy<T>(payload: TersePayload): T {\n if (Array.isArray(payload.d)) {\n return payload.d.map(item => {\n if (typeof item === 'object' && item !== null && !Array.isArray(item)) {\n return createTerseProxy(item as Record<string, unknown>, payload.k);\n }\n return item;\n }) as T;\n }\n\n if (typeof payload.d === 'object' && payload.d !== null) {\n return createTerseProxy(payload.d as Record<string, unknown>, payload.k) as T;\n }\n\n return payload.d as T;\n}\n\n// Re-export for convenience\nexport { isTersePayload };\n","/**\n * TerseJSON Server-Side Memory Optimization\n *\n * Utilities for memory-efficient data handling on the server:\n * - TerseCache: Store compressed data, return Proxy-wrapped on access\n * - compressStream: Compress data as it streams from database cursors\n * - createTerseServiceClient: Inter-service communication with compressed payloads\n */\n\nimport { compress, wrapWithProxy, isTersePayload } from './core';\nimport type { TersePayload, CompressOptions } from './types';\n\n/**\n * Options for TerseCache\n */\nexport interface TerseCacheOptions {\n /** Compression options passed to compress() */\n compressOptions?: CompressOptions;\n /** Maximum number of entries (simple LRU when exceeded) */\n maxSize?: number;\n /** Default TTL in milliseconds (optional) */\n defaultTTL?: number;\n}\n\n/**\n * A memory-efficient cache that stores data in compressed form.\n *\n * Data is compressed on set() and stays compressed in memory.\n * On get(), data is returned wrapped in a Proxy for lazy expansion.\n *\n * @example\n * ```typescript\n * const cache = new TerseCache<User[]>();\n *\n * // Store compressed - uses less memory\n * cache.set('users', largeUserArray);\n *\n * // Get returns Proxy-wrapped data\n * const users = cache.get('users');\n * users?.map(u => u.name); // Only 'name' field expands\n * ```\n */\nexport class TerseCache<T extends Record<string, unknown>[] = Record<string, unknown>[]> {\n private cache: Map<string, { payload: TersePayload<T>; expiresAt?: number }>;\n private options: TerseCacheOptions;\n private accessOrder: string[] = [];\n\n constructor(options: TerseCacheOptions = {}) {\n this.cache = new Map();\n this.options = options;\n }\n\n /**\n * Store data in compressed form\n */\n set(key: string, data: T, ttl?: number): void {\n // Enforce max size with simple LRU eviction\n if (this.options.maxSize && this.cache.size >= this.options.maxSize) {\n const oldestKey = this.accessOrder.shift();\n if (oldestKey) {\n this.cache.delete(oldestKey);\n }\n }\n\n const payload = compress(data, this.options.compressOptions) as TersePayload<T>;\n const effectiveTTL = ttl ?? this.options.defaultTTL;\n const expiresAt = effectiveTTL ? Date.now() + effectiveTTL : undefined;\n\n this.cache.set(key, { payload, expiresAt });\n this.updateAccessOrder(key);\n }\n\n /**\n * Get data wrapped in Proxy for lazy expansion.\n * Only accessed fields are expanded from compressed form.\n */\n get(key: string): T | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n\n // Check TTL\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n this.removeFromAccessOrder(key);\n return undefined;\n }\n\n this.updateAccessOrder(key);\n return wrapWithProxy<T>(entry.payload);\n }\n\n /**\n * Get the raw compressed payload without expansion.\n * Useful for forwarding to other services.\n */\n getRaw(key: string): TersePayload<T> | undefined {\n const entry = this.cache.get(key);\n if (!entry) return undefined;\n\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n this.removeFromAccessOrder(key);\n return undefined;\n }\n\n return entry.payload;\n }\n\n /**\n * Check if key exists (and is not expired)\n */\n has(key: string): boolean {\n const entry = this.cache.get(key);\n if (!entry) return false;\n\n if (entry.expiresAt && Date.now() > entry.expiresAt) {\n this.cache.delete(key);\n this.removeFromAccessOrder(key);\n return false;\n }\n\n return true;\n }\n\n /**\n * Delete an entry\n */\n delete(key: string): boolean {\n this.removeFromAccessOrder(key);\n return this.cache.delete(key);\n }\n\n /**\n * Clear all entries\n */\n clear(): void {\n this.cache.clear();\n this.accessOrder = [];\n }\n\n /**\n * Get current cache size\n */\n get size(): number {\n return this.cache.size;\n }\n\n private updateAccessOrder(key: string): void {\n this.removeFromAccessOrder(key);\n this.accessOrder.push(key);\n }\n\n private removeFromAccessOrder(key: string): void {\n const index = this.accessOrder.indexOf(key);\n if (index > -1) {\n this.accessOrder.splice(index, 1);\n }\n }\n}\n\n/**\n * Options for streaming compression\n */\nexport interface CompressStreamOptions extends CompressOptions {\n /** Number of items per batch (default: 100) */\n batchSize?: number;\n}\n\n/**\n * Compress data as it streams from an async source (e.g., database cursor).\n *\n * Yields compressed batches without loading entire result set into memory.\n *\n * @example\n * ```typescript\n * // MongoDB cursor\n * const cursor = db.collection('users').find().stream();\n * for await (const batch of compressStream(cursor, { batchSize: 100 })) {\n * // Send batch to client or process\n * res.write(JSON.stringify(batch));\n * }\n *\n * // PostgreSQL stream\n * const stream = client.query(new QueryStream('SELECT * FROM users'));\n * for await (const batch of compressStream(stream)) {\n * // Process compressed batches\n * }\n * ```\n */\nexport async function* compressStream<T extends Record<string, unknown>>(\n source: AsyncIterable<T>,\n options: CompressStreamOptions = {}\n): AsyncGenerator<TersePayload<T[]>, void, unknown> {\n const batchSize = options.batchSize ?? 100;\n const { batchSize: _, ...compressOptions } = options;\n\n let batch: T[] = [];\n\n for await (const item of source) {\n batch.push(item);\n\n if (batch.length >= batchSize) {\n yield compress(batch, compressOptions) as TersePayload<T[]>;\n batch = [];\n }\n }\n\n // Yield remaining items\n if (batch.length > 0) {\n yield compress(batch, compressOptions) as TersePayload<T[]>;\n }\n}\n\n/**\n * Configuration for TerseServiceClient\n */\nexport interface TerseServiceClientConfig {\n /** Base URL for the service */\n baseUrl: string;\n /** Whether to expand responses with Proxy (default: true) */\n expandOnReceive?: boolean;\n /** Custom headers to include in all requests */\n headers?: Record<string, string>;\n /** Custom fetch implementation (for testing or Node.js < 18) */\n fetch?: typeof globalThis.fetch;\n}\n\n/**\n * HTTP client for inter-service communication with TerseJSON.\n *\n * Services can pass compressed data without intermediate expansion,\n * reducing memory usage across the request chain.\n *\n * @example\n * ```typescript\n * const serviceB = createTerseServiceClient({\n * baseUrl: 'http://service-b:3000',\n * });\n *\n * // GET - returns Proxy-wrapped data\n * const users = await serviceB.get<User[]>('/api/users');\n * users.map(u => u.name); // Only accessed fields expand\n *\n * // Forward compressed payload to another service\n * const raw = cache.getRaw('users');\n * if (raw) {\n * await serviceB.forward('/api/users/sync', raw);\n * }\n * ```\n */\nexport function createTerseServiceClient(config: TerseServiceClientConfig) {\n const {\n baseUrl,\n expandOnReceive = true,\n headers: customHeaders = {},\n fetch: customFetch = globalThis.fetch,\n } = config;\n\n const defaultHeaders = {\n 'Accept-Terse': 'true',\n 'Content-Type': 'application/json',\n ...customHeaders,\n };\n\n return {\n /**\n * GET request - receives TerseJSON and returns Proxy-wrapped (or raw)\n */\n async get<T>(path: string, options?: { headers?: Record<string, string> }): Promise<T> {\n const response = await customFetch(`${baseUrl}${path}`, {\n method: 'GET',\n headers: { ...defaultHeaders, ...options?.headers },\n });\n\n if (!response.ok) {\n throw new Error(`TerseServiceClient: GET ${path} failed with status ${response.status}`);\n }\n\n const data = await response.json();\n\n if (isTersePayload(data) && expandOnReceive) {\n return wrapWithProxy<T>(data as TersePayload<T extends unknown[] ? T : never>);\n }\n\n return data as T;\n },\n\n /**\n * POST request with automatic compression\n */\n async post<T, R = unknown>(\n path: string,\n data: T,\n options?: { headers?: Record<string, string>; compress?: boolean }\n ): Promise<R> {\n const shouldCompress = options?.compress ?? Array.isArray(data);\n const body = shouldCompress && Array.isArray(data)\n ? JSON.stringify(compress(data as Record<string, unknown>[]))\n : JSON.stringify(data);\n\n const headers = {\n ...defaultHeaders,\n ...options?.headers,\n ...(shouldCompress ? { 'X-Terse-JSON': 'true' } : {}),\n };\n\n const response = await customFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers,\n body,\n });\n\n if (!response.ok) {\n throw new Error(`TerseServiceClient: POST ${path} failed with status ${response.status}`);\n }\n\n const responseData = await response.json();\n\n if (isTersePayload(responseData) && expandOnReceive) {\n return wrapWithProxy<R>(responseData as TersePayload<R extends unknown[] ? R : never>);\n }\n\n return responseData as R;\n },\n\n /**\n * Forward a raw TersePayload to another endpoint without expansion.\n * Useful for passing data between services without intermediate deserialization.\n */\n async forward<T extends Record<string, unknown>[]>(\n path: string,\n payload: TersePayload<T>,\n options?: { headers?: Record<string, string> }\n ): Promise<void> {\n const response = await customFetch(`${baseUrl}${path}`, {\n method: 'POST',\n headers: {\n ...defaultHeaders,\n ...options?.headers,\n 'X-Terse-JSON': 'true',\n },\n body: JSON.stringify(payload),\n });\n\n if (!response.ok) {\n throw new Error(`TerseServiceClient: forward to ${path} failed with status ${response.status}`);\n }\n },\n };\n}\n\n// Re-export core types for convenience\nexport type { TersePayload, CompressOptions };\n"]}
package/package.json CHANGED
@@ -1,9 +1,15 @@
1
1
  {
2
2
  "name": "tersejson",
3
- "version": "0.2.1",
4
- "description": "Transparent JSON key compression for Express APIs. Reduce bandwidth by up to 80% with zero code changes.",
3
+ "version": "0.3.1",
4
+ "description": "Memory-efficient JSON processing. Lazy Proxy expansion uses 70% less RAM than JSON.parse - plus 30-80% smaller payloads.",
5
5
  "keywords": [
6
6
  "json",
7
+ "memory-efficient",
8
+ "lazy-loading",
9
+ "proxy",
10
+ "low-memory",
11
+ "gc-friendly",
12
+ "partial-deserialization",
7
13
  "compression",
8
14
  "express",
9
15
  "middleware",
@@ -17,11 +23,8 @@
17
23
  "payload",
18
24
  "reduce",
19
25
  "compact",
20
- "shrink",
21
26
  "performance",
22
27
  "network",
23
- "http",
24
- "response",
25
28
  "nodejs",
26
29
  "node",
27
30
  "fetch",
@@ -29,13 +32,12 @@
29
32
  "react-query",
30
33
  "swr",
31
34
  "angular",
32
- "restful",
35
+ "mongodb",
36
+ "mongoose",
37
+ "database",
33
38
  "key-compression",
34
- "json-minify",
35
39
  "api-optimization",
36
- "payload-compression",
37
40
  "lightweight",
38
- "proxy",
39
41
  "transparent"
40
42
  ],
41
43
  "author": "Tim Carter",
@@ -86,6 +88,16 @@
86
88
  "types": "./dist/graphql-client.d.ts",
87
89
  "import": "./dist/graphql-client.mjs",
88
90
  "require": "./dist/graphql-client.js"
91
+ },
92
+ "./server-memory": {
93
+ "types": "./dist/server-memory.d.ts",
94
+ "import": "./dist/server-memory.mjs",
95
+ "require": "./dist/server-memory.js"
96
+ },
97
+ "./mongodb": {
98
+ "types": "./dist/mongodb.d.ts",
99
+ "import": "./dist/mongodb.mjs",
100
+ "require": "./dist/mongodb.js"
89
101
  }
90
102
  },
91
103
  "files": [
@@ -105,14 +117,16 @@
105
117
  "@types/node": "^20.11.0",
106
118
  "eslint": "^8.56.0",
107
119
  "express": "^4.18.2",
120
+ "mongodb": "^7.0.0",
108
121
  "sharp": "^0.34.5",
109
122
  "tsup": "^8.0.1",
110
123
  "typescript": "^5.3.3",
111
124
  "vitest": "^1.2.0"
112
125
  },
113
126
  "peerDependencies": {
127
+ "@apollo/client": ">=3.0.0",
114
128
  "express": ">=4.0.0",
115
- "@apollo/client": ">=3.0.0"
129
+ "mongodb": ">=5.0.0"
116
130
  },
117
131
  "peerDependenciesMeta": {
118
132
  "express": {
@@ -120,6 +134,9 @@
120
134
  },
121
135
  "@apollo/client": {
122
136
  "optional": true
137
+ },
138
+ "mongodb": {
139
+ "optional": true
123
140
  }
124
141
  },
125
142
  "engines": {