@nicia-ai/typegraph 0.1.0

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.
Files changed (131) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/ast-BVyihVbP.d.cts +564 -0
  4. package/dist/ast-BVyihVbP.d.ts +564 -0
  5. package/dist/backend/drizzle/index.cjs +41 -0
  6. package/dist/backend/drizzle/index.cjs.map +1 -0
  7. package/dist/backend/drizzle/index.d.cts +12 -0
  8. package/dist/backend/drizzle/index.d.ts +12 -0
  9. package/dist/backend/drizzle/index.js +12 -0
  10. package/dist/backend/drizzle/index.js.map +1 -0
  11. package/dist/backend/drizzle/postgres.cjs +27 -0
  12. package/dist/backend/drizzle/postgres.cjs.map +1 -0
  13. package/dist/backend/drizzle/postgres.d.cts +37 -0
  14. package/dist/backend/drizzle/postgres.d.ts +37 -0
  15. package/dist/backend/drizzle/postgres.js +10 -0
  16. package/dist/backend/drizzle/postgres.js.map +1 -0
  17. package/dist/backend/drizzle/schema/postgres.cjs +40 -0
  18. package/dist/backend/drizzle/schema/postgres.cjs.map +1 -0
  19. package/dist/backend/drizzle/schema/postgres.d.cts +2419 -0
  20. package/dist/backend/drizzle/schema/postgres.d.ts +2419 -0
  21. package/dist/backend/drizzle/schema/postgres.js +7 -0
  22. package/dist/backend/drizzle/schema/postgres.js.map +1 -0
  23. package/dist/backend/drizzle/schema/sqlite.cjs +40 -0
  24. package/dist/backend/drizzle/schema/sqlite.cjs.map +1 -0
  25. package/dist/backend/drizzle/schema/sqlite.d.cts +2647 -0
  26. package/dist/backend/drizzle/schema/sqlite.d.ts +2647 -0
  27. package/dist/backend/drizzle/schema/sqlite.js +7 -0
  28. package/dist/backend/drizzle/schema/sqlite.js.map +1 -0
  29. package/dist/backend/drizzle/sqlite.cjs +27 -0
  30. package/dist/backend/drizzle/sqlite.cjs.map +1 -0
  31. package/dist/backend/drizzle/sqlite.d.cts +36 -0
  32. package/dist/backend/drizzle/sqlite.d.ts +36 -0
  33. package/dist/backend/drizzle/sqlite.js +10 -0
  34. package/dist/backend/drizzle/sqlite.js.map +1 -0
  35. package/dist/backend/postgres/index.cjs +53 -0
  36. package/dist/backend/postgres/index.cjs.map +1 -0
  37. package/dist/backend/postgres/index.d.cts +12 -0
  38. package/dist/backend/postgres/index.d.ts +12 -0
  39. package/dist/backend/postgres/index.js +12 -0
  40. package/dist/backend/postgres/index.js.map +1 -0
  41. package/dist/backend/sqlite/index.cjs +117 -0
  42. package/dist/backend/sqlite/index.cjs.map +1 -0
  43. package/dist/backend/sqlite/index.d.cts +71 -0
  44. package/dist/backend/sqlite/index.d.ts +71 -0
  45. package/dist/backend/sqlite/index.js +78 -0
  46. package/dist/backend/sqlite/index.js.map +1 -0
  47. package/dist/chunk-2QHQ2C4P.js +146 -0
  48. package/dist/chunk-2QHQ2C4P.js.map +1 -0
  49. package/dist/chunk-3A5TKOEJ.js +306 -0
  50. package/dist/chunk-3A5TKOEJ.js.map +1 -0
  51. package/dist/chunk-4PIEL2VO.js +162 -0
  52. package/dist/chunk-4PIEL2VO.js.map +1 -0
  53. package/dist/chunk-536PH5FT.js +342 -0
  54. package/dist/chunk-536PH5FT.js.map +1 -0
  55. package/dist/chunk-DBFCKELK.cjs +156 -0
  56. package/dist/chunk-DBFCKELK.cjs.map +1 -0
  57. package/dist/chunk-DDM2FZRJ.cjs +1143 -0
  58. package/dist/chunk-DDM2FZRJ.cjs.map +1 -0
  59. package/dist/chunk-DGUM43GV.js +10 -0
  60. package/dist/chunk-DGUM43GV.js.map +1 -0
  61. package/dist/chunk-F32HCHYA.cjs +680 -0
  62. package/dist/chunk-F32HCHYA.cjs.map +1 -0
  63. package/dist/chunk-IIAT36MI.js +353 -0
  64. package/dist/chunk-IIAT36MI.js.map +1 -0
  65. package/dist/chunk-JDAET5LO.js +236 -0
  66. package/dist/chunk-JDAET5LO.js.map +1 -0
  67. package/dist/chunk-JEQ2X3Z6.cjs +12 -0
  68. package/dist/chunk-JEQ2X3Z6.cjs.map +1 -0
  69. package/dist/chunk-JKTO7TW3.js +299 -0
  70. package/dist/chunk-JKTO7TW3.js.map +1 -0
  71. package/dist/chunk-K7SQ3SWP.js +497 -0
  72. package/dist/chunk-K7SQ3SWP.js.map +1 -0
  73. package/dist/chunk-L642L24T.js +142 -0
  74. package/dist/chunk-L642L24T.js.map +1 -0
  75. package/dist/chunk-MFVCSNIY.cjs +308 -0
  76. package/dist/chunk-MFVCSNIY.cjs.map +1 -0
  77. package/dist/chunk-MNO33ASC.cjs +240 -0
  78. package/dist/chunk-MNO33ASC.cjs.map +1 -0
  79. package/dist/chunk-N4AOJ3VF.cjs +154 -0
  80. package/dist/chunk-N4AOJ3VF.cjs.map +1 -0
  81. package/dist/chunk-P5CNM325.cjs +508 -0
  82. package/dist/chunk-P5CNM325.cjs.map +1 -0
  83. package/dist/chunk-RYT4H46I.js +646 -0
  84. package/dist/chunk-RYT4H46I.js.map +1 -0
  85. package/dist/chunk-SV5H3XM5.cjs +321 -0
  86. package/dist/chunk-SV5H3XM5.cjs.map +1 -0
  87. package/dist/chunk-TXHKFLWX.cjs +344 -0
  88. package/dist/chunk-TXHKFLWX.cjs.map +1 -0
  89. package/dist/chunk-UJAGXJDG.cjs +170 -0
  90. package/dist/chunk-UJAGXJDG.cjs.map +1 -0
  91. package/dist/chunk-VXRVGFCI.js +1128 -0
  92. package/dist/chunk-VXRVGFCI.js.map +1 -0
  93. package/dist/chunk-YM5AL65Y.cjs +357 -0
  94. package/dist/chunk-YM5AL65Y.cjs.map +1 -0
  95. package/dist/index.cjs +8334 -0
  96. package/dist/index.cjs.map +1 -0
  97. package/dist/index.d.cts +1365 -0
  98. package/dist/index.d.ts +1365 -0
  99. package/dist/index.js +8105 -0
  100. package/dist/index.js.map +1 -0
  101. package/dist/indexes/index.cjs +67 -0
  102. package/dist/indexes/index.cjs.map +1 -0
  103. package/dist/indexes/index.d.cts +62 -0
  104. package/dist/indexes/index.d.ts +62 -0
  105. package/dist/indexes/index.js +6 -0
  106. package/dist/indexes/index.js.map +1 -0
  107. package/dist/interchange/index.cjs +612 -0
  108. package/dist/interchange/index.cjs.map +1 -0
  109. package/dist/interchange/index.d.cts +288 -0
  110. package/dist/interchange/index.d.ts +288 -0
  111. package/dist/interchange/index.js +598 -0
  112. package/dist/interchange/index.js.map +1 -0
  113. package/dist/profiler/index.cjs +793 -0
  114. package/dist/profiler/index.cjs.map +1 -0
  115. package/dist/profiler/index.d.cts +283 -0
  116. package/dist/profiler/index.d.ts +283 -0
  117. package/dist/profiler/index.js +785 -0
  118. package/dist/profiler/index.js.map +1 -0
  119. package/dist/store-60Lcfi0w.d.ts +2263 -0
  120. package/dist/store-Bifii8MZ.d.cts +2263 -0
  121. package/dist/test-helpers-BjyRYJZX.d.ts +22 -0
  122. package/dist/test-helpers-NoQXhleQ.d.cts +22 -0
  123. package/dist/types-BRzHlhKC.d.cts +14 -0
  124. package/dist/types-BRzHlhKC.d.ts +14 -0
  125. package/dist/types-BrSfFSpW.d.cts +158 -0
  126. package/dist/types-CX4cLd7M.d.ts +152 -0
  127. package/dist/types-CjZ7g_7v.d.ts +442 -0
  128. package/dist/types-DDOSfrih.d.cts +442 -0
  129. package/dist/types-D_3mEv2y.d.ts +158 -0
  130. package/dist/types-a5rAxC92.d.cts +152 -0
  131. package/package.json +201 -0
@@ -0,0 +1,793 @@
1
+ 'use strict';
2
+
3
+ var chunkP5CNM325_cjs = require('../chunk-P5CNM325.cjs');
4
+ require('../chunk-JEQ2X3Z6.cjs');
5
+
6
+ // src/profiler/ast-extractor.ts
7
+ function extractPropertyAccesses(ast) {
8
+ const accesses = [];
9
+ for (const predicate of ast.predicates) {
10
+ const extracted = extractFromNodePredicate(predicate, ast);
11
+ accesses.push(...extracted);
12
+ }
13
+ if (ast.orderBy) {
14
+ for (const order of ast.orderBy) {
15
+ const extracted = extractFromOrderSpec(order, ast);
16
+ if (extracted) {
17
+ accesses.push(extracted);
18
+ }
19
+ }
20
+ }
21
+ for (const field of ast.projection.fields) {
22
+ const extracted = extractFromProjectedField(field, ast);
23
+ if (extracted) {
24
+ accesses.push(extracted);
25
+ }
26
+ }
27
+ if (ast.groupBy) {
28
+ for (const field of ast.groupBy.fields) {
29
+ const extracted = extractFromFieldRef(field, "groupBy", ast);
30
+ if (extracted) {
31
+ accesses.push(extracted);
32
+ }
33
+ }
34
+ }
35
+ if (ast.having) {
36
+ const havingAccesses = extractFromExpression(ast.having, void 0, ast);
37
+ accesses.push(...havingAccesses);
38
+ }
39
+ if (ast.selectiveFields) {
40
+ for (const field of ast.selectiveFields) {
41
+ const extracted = extractFromSelectiveField(field, ast);
42
+ if (extracted) {
43
+ accesses.push(extracted);
44
+ }
45
+ }
46
+ }
47
+ return accesses;
48
+ }
49
+ function extractFromNodePredicate(predicate, ast) {
50
+ return extractFromExpression(
51
+ predicate.expression,
52
+ predicate.targetAlias,
53
+ ast
54
+ );
55
+ }
56
+ function extractFromExpression(expr, alias, ast) {
57
+ const accesses = [];
58
+ switch (expr.__type) {
59
+ case "comparison": {
60
+ const extracted = extractFromFieldRef(expr.left, "filter", ast);
61
+ if (extracted) {
62
+ accesses.push({ ...extracted, predicateType: expr.op });
63
+ }
64
+ break;
65
+ }
66
+ case "string_op": {
67
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
68
+ if (extracted) {
69
+ accesses.push({ ...extracted, predicateType: expr.op });
70
+ }
71
+ break;
72
+ }
73
+ case "null_check": {
74
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
75
+ if (extracted) {
76
+ accesses.push({ ...extracted, predicateType: expr.op });
77
+ }
78
+ break;
79
+ }
80
+ case "between": {
81
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
82
+ if (extracted) {
83
+ accesses.push({ ...extracted, predicateType: "between" });
84
+ }
85
+ break;
86
+ }
87
+ case "array_op": {
88
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
89
+ if (extracted) {
90
+ accesses.push({ ...extracted, predicateType: expr.op });
91
+ }
92
+ break;
93
+ }
94
+ case "object_op": {
95
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
96
+ if (extracted) {
97
+ accesses.push({ ...extracted, predicateType: expr.op });
98
+ }
99
+ break;
100
+ }
101
+ case "and":
102
+ case "or": {
103
+ for (const sub of expr.predicates) {
104
+ accesses.push(...extractFromExpression(sub, alias, ast));
105
+ }
106
+ break;
107
+ }
108
+ case "not": {
109
+ accesses.push(...extractFromExpression(expr.predicate, alias, ast));
110
+ break;
111
+ }
112
+ case "aggregate_comparison": {
113
+ const extracted = extractFromFieldRef(
114
+ expr.aggregate.field,
115
+ "filter",
116
+ ast
117
+ );
118
+ if (extracted) {
119
+ accesses.push({
120
+ ...extracted,
121
+ predicateType: `${expr.aggregate.function}_${expr.op}`
122
+ });
123
+ }
124
+ break;
125
+ }
126
+ case "exists": {
127
+ const subAccesses = extractPropertyAccesses(expr.subquery);
128
+ accesses.push(...subAccesses);
129
+ break;
130
+ }
131
+ case "in_subquery": {
132
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
133
+ if (extracted) {
134
+ accesses.push({ ...extracted, predicateType: "in_subquery" });
135
+ }
136
+ const subAccesses = extractPropertyAccesses(expr.subquery);
137
+ accesses.push(...subAccesses);
138
+ break;
139
+ }
140
+ case "vector_similarity": {
141
+ const extracted = extractFromFieldRef(expr.field, "filter", ast);
142
+ if (extracted) {
143
+ accesses.push({ ...extracted, predicateType: "vector_similarity" });
144
+ }
145
+ break;
146
+ }
147
+ }
148
+ return accesses;
149
+ }
150
+ function extractFromOrderSpec(order, ast) {
151
+ return extractFromFieldRef(order.field, "sort", ast);
152
+ }
153
+ function extractFromProjectedField(field, ast) {
154
+ if (field.source.__type === "aggregate") {
155
+ return extractFromFieldRef(field.source.field, "select", ast);
156
+ }
157
+ return extractFromFieldRef(field.source, "select", ast);
158
+ }
159
+ function extractFromSelectiveField(field, ast) {
160
+ if (field.isSystemField) {
161
+ return void 0;
162
+ }
163
+ const resolved = resolveAliasToKinds(field.alias, ast);
164
+ if (!resolved) {
165
+ return void 0;
166
+ }
167
+ return {
168
+ entityType: resolved.entityType,
169
+ kindNames: resolved.kindNames,
170
+ target: {
171
+ __type: "prop",
172
+ pointer: chunkP5CNM325_cjs.jsonPointer(field.field.split("."))
173
+ },
174
+ context: "select"
175
+ };
176
+ }
177
+ function extractFromFieldRef(ref, context, ast) {
178
+ const resolved = resolveAliasToKinds(ref.alias, ast);
179
+ if (!resolved) {
180
+ return void 0;
181
+ }
182
+ const target = extractTargetFromFieldRef(ref);
183
+ if (!target) {
184
+ return void 0;
185
+ }
186
+ return {
187
+ entityType: resolved.entityType,
188
+ kindNames: resolved.kindNames,
189
+ target,
190
+ context
191
+ };
192
+ }
193
+ function resolveAliasToKinds(alias, ast) {
194
+ if (ast.start.alias === alias) {
195
+ return { entityType: "node", kindNames: ast.start.kinds };
196
+ }
197
+ for (const traversal of ast.traversals) {
198
+ if (traversal.nodeAlias === alias) {
199
+ return { entityType: "node", kindNames: traversal.nodeKinds };
200
+ }
201
+ if (traversal.edgeAlias === alias) {
202
+ return { entityType: "edge", kindNames: traversal.edgeKinds };
203
+ }
204
+ }
205
+ return void 0;
206
+ }
207
+ function extractTargetFromFieldRef(ref) {
208
+ if (ref.jsonPointer) {
209
+ return { __type: "prop", pointer: ref.jsonPointer };
210
+ }
211
+ if (ref.path.length > 1 && ref.path[0] === "props") {
212
+ return { __type: "prop", pointer: chunkP5CNM325_cjs.jsonPointer(ref.path.slice(1)) };
213
+ }
214
+ if (ref.path.length === 1) {
215
+ return { __type: "system", field: ref.path[0] };
216
+ }
217
+ return void 0;
218
+ }
219
+
220
+ // src/profiler/collector.ts
221
+ function pathToKey(path) {
222
+ const targetKey = path.target.__type === "prop" ? path.target.pointer : `$${path.target.field}`;
223
+ return `${path.entityType}:${path.kind}:${targetKey}`;
224
+ }
225
+ function keyToPath(key) {
226
+ const firstColonIndex = key.indexOf(":");
227
+ const secondColonIndex = firstColonIndex === -1 ? -1 : key.indexOf(":", firstColonIndex + 1);
228
+ if (firstColonIndex === -1 || secondColonIndex === -1) {
229
+ throw new Error(
230
+ `Invalid profile key: "${key}". Expected format "{entityType}:{kind}:{target}".`
231
+ );
232
+ }
233
+ const entityTypePart = key.slice(0, firstColonIndex);
234
+ const kind = key.slice(firstColonIndex + 1, secondColonIndex);
235
+ if (kind === "") {
236
+ throw new Error(`Invalid profile key: "${key}". Kind must not be empty.`);
237
+ }
238
+ const targetPart = key.slice(secondColonIndex + 1);
239
+ const entityType = parseEntityType(entityTypePart);
240
+ if (targetPart.startsWith("$")) {
241
+ const systemField = targetPart.slice(1);
242
+ if (systemField === "") {
243
+ throw new Error(
244
+ `Invalid profile key: "${key}". System field must not be empty.`
245
+ );
246
+ }
247
+ return {
248
+ entityType,
249
+ kind,
250
+ target: { __type: "system", field: systemField }
251
+ };
252
+ }
253
+ const pointer = chunkP5CNM325_cjs.normalizeJsonPointer(
254
+ targetPart
255
+ );
256
+ return {
257
+ entityType,
258
+ kind,
259
+ target: { __type: "prop", pointer }
260
+ };
261
+ }
262
+ function parseEntityType(value) {
263
+ if (value === "node" || value === "edge") {
264
+ return value;
265
+ }
266
+ throw new Error(
267
+ `Invalid entity type in profile key: "${value}". Expected "node" or "edge".`
268
+ );
269
+ }
270
+ var ProfileCollector = class {
271
+ #patterns = /* @__PURE__ */ new Map();
272
+ #startedAt = /* @__PURE__ */ new Date();
273
+ #queryCount = 0;
274
+ /**
275
+ * Records a property access.
276
+ *
277
+ * @param path - The property path that was accessed
278
+ * @param context - How the property was used (filter, sort, select, groupBy)
279
+ * @param predicateType - Optional predicate type (eq, contains, gt, etc.)
280
+ */
281
+ record(path, context, predicateType) {
282
+ const key = pathToKey(path);
283
+ const existing = this.#patterns.get(key);
284
+ const nowMs = Date.now();
285
+ if (existing) {
286
+ existing.count++;
287
+ existing.contexts.add(context);
288
+ if (predicateType) {
289
+ existing.predicateTypes.add(predicateType);
290
+ }
291
+ existing.lastSeenMs = nowMs;
292
+ } else {
293
+ this.#patterns.set(key, {
294
+ count: 1,
295
+ contexts: /* @__PURE__ */ new Set([context]),
296
+ predicateTypes: predicateType ? /* @__PURE__ */ new Set([predicateType]) : /* @__PURE__ */ new Set(),
297
+ firstSeenMs: nowMs,
298
+ lastSeenMs: nowMs
299
+ });
300
+ }
301
+ }
302
+ /**
303
+ * Increments the query count.
304
+ * Call this once per query execution.
305
+ */
306
+ recordQuery() {
307
+ this.#queryCount++;
308
+ }
309
+ /**
310
+ * Gets all patterns as an immutable map.
311
+ *
312
+ * Returns a new Map with immutable stats objects.
313
+ */
314
+ getPatterns() {
315
+ const result = /* @__PURE__ */ new Map();
316
+ for (const [key, stats] of this.#patterns) {
317
+ result.set(key, {
318
+ count: stats.count,
319
+ contexts: new Set(stats.contexts),
320
+ predicateTypes: new Set(stats.predicateTypes),
321
+ firstSeen: new Date(stats.firstSeenMs),
322
+ lastSeen: new Date(stats.lastSeenMs)
323
+ });
324
+ }
325
+ return result;
326
+ }
327
+ /**
328
+ * Gets summary statistics.
329
+ */
330
+ getSummary() {
331
+ return {
332
+ totalQueries: this.#queryCount,
333
+ uniquePatterns: this.#patterns.size,
334
+ startedAt: this.#startedAt,
335
+ durationMs: Date.now() - this.#startedAt.getTime()
336
+ };
337
+ }
338
+ /**
339
+ * Resets all collected data.
340
+ *
341
+ * Note: This does not reset the startedAt timestamp.
342
+ * Create a new ProfileCollector for a fresh session.
343
+ */
344
+ reset() {
345
+ this.#patterns.clear();
346
+ this.#queryCount = 0;
347
+ }
348
+ };
349
+
350
+ // src/profiler/constants.ts
351
+ var DEFAULT_MIN_FREQUENCY_FOR_RECOMMENDATION = 3;
352
+ var DEFAULT_HIGH_FREQUENCY_THRESHOLD = 10;
353
+ var DEFAULT_MEDIUM_FREQUENCY_THRESHOLD = 5;
354
+
355
+ // src/profiler/recommendations.ts
356
+ function generateRecommendations(patterns, declaredIndexes, options = {}) {
357
+ const recommendations = [];
358
+ const indexedPaths = buildIndexedPathsSet(declaredIndexes);
359
+ const config = normalizeRecommendationsOptions(options);
360
+ for (const [key, stats] of patterns) {
361
+ const hasFilterOrSort = stats.contexts.has("filter") || stats.contexts.has("sort");
362
+ if (!hasFilterOrSort) {
363
+ continue;
364
+ }
365
+ if (stats.count < config.minFrequencyForRecommendation) {
366
+ continue;
367
+ }
368
+ const path = keyToPath(key);
369
+ if (isSystemField(path)) {
370
+ continue;
371
+ }
372
+ if (isIndexed(path, indexedPaths)) {
373
+ continue;
374
+ }
375
+ recommendations.push({
376
+ entityType: path.entityType,
377
+ kind: path.kind,
378
+ fields: path.target.__type === "prop" ? [path.target.pointer] : [],
379
+ reason: buildReasonString(stats),
380
+ frequency: stats.count,
381
+ priority: getPriority(stats.count, config)
382
+ });
383
+ }
384
+ return recommendations.toSorted((a, b) => {
385
+ const priorityOrder = {
386
+ high: 0,
387
+ medium: 1,
388
+ low: 2
389
+ };
390
+ const priorityDiff = priorityOrder[a.priority] - priorityOrder[b.priority];
391
+ if (priorityDiff !== 0) return priorityDiff;
392
+ return b.frequency - a.frequency;
393
+ });
394
+ }
395
+ function getUnindexedFilters(patterns, declaredIndexes) {
396
+ const indexedPaths = buildIndexedPathsSet(declaredIndexes);
397
+ const unindexed = [];
398
+ for (const [key, stats] of patterns) {
399
+ if (!stats.contexts.has("filter")) {
400
+ continue;
401
+ }
402
+ const path = keyToPath(key);
403
+ if (isSystemField(path)) {
404
+ continue;
405
+ }
406
+ if (!isIndexed(path, indexedPaths)) {
407
+ unindexed.push(path);
408
+ }
409
+ }
410
+ return unindexed;
411
+ }
412
+ function buildIndexedPathsSet(indexes) {
413
+ const set = /* @__PURE__ */ new Set();
414
+ for (const index of indexes) {
415
+ const firstField = index.fields[0];
416
+ if (!firstField) continue;
417
+ set.add(
418
+ pathToKey({
419
+ entityType: index.entityType,
420
+ kind: index.kind,
421
+ target: { __type: "prop", pointer: firstField }
422
+ })
423
+ );
424
+ }
425
+ return set;
426
+ }
427
+ function isIndexed(path, indexedPaths) {
428
+ return indexedPaths.has(pathToKey(path));
429
+ }
430
+ function isSystemField(path) {
431
+ return path.target.__type === "system";
432
+ }
433
+ function buildReasonString(stats) {
434
+ const contexts = [...stats.contexts].join(", ");
435
+ const predicates = [...stats.predicateTypes];
436
+ let reason = `Used in ${contexts}`;
437
+ if (predicates.length > 0) {
438
+ reason += ` with ${predicates.join(", ")}`;
439
+ }
440
+ reason += ` (${stats.count} ${stats.count === 1 ? "time" : "times"})`;
441
+ return reason;
442
+ }
443
+ function normalizeRecommendationsOptions(options) {
444
+ if (typeof options === "number") {
445
+ return {
446
+ minFrequencyForRecommendation: options,
447
+ mediumFrequencyThreshold: DEFAULT_MEDIUM_FREQUENCY_THRESHOLD,
448
+ highFrequencyThreshold: DEFAULT_HIGH_FREQUENCY_THRESHOLD
449
+ };
450
+ }
451
+ return {
452
+ minFrequencyForRecommendation: options.minFrequencyForRecommendation ?? DEFAULT_MIN_FREQUENCY_FOR_RECOMMENDATION,
453
+ mediumFrequencyThreshold: options.mediumFrequencyThreshold ?? DEFAULT_MEDIUM_FREQUENCY_THRESHOLD,
454
+ highFrequencyThreshold: options.highFrequencyThreshold ?? DEFAULT_HIGH_FREQUENCY_THRESHOLD
455
+ };
456
+ }
457
+ function getPriority(frequency, options) {
458
+ if (frequency >= options.highFrequencyThreshold) return "high";
459
+ if (frequency >= options.mediumFrequencyThreshold) return "medium";
460
+ return "low";
461
+ }
462
+
463
+ // src/profiler/query-profiler.ts
464
+ var QueryProfiler = class {
465
+ #collector = new ProfileCollector();
466
+ #declaredIndexes;
467
+ #minFrequencyForRecommendation;
468
+ #mediumFrequencyThreshold;
469
+ #highFrequencyThreshold;
470
+ #schemaIntrospector;
471
+ #attached = false;
472
+ constructor(options) {
473
+ this.#declaredIndexes = options?.declaredIndexes ?? [];
474
+ this.#minFrequencyForRecommendation = options?.minFrequencyForRecommendation ?? DEFAULT_MIN_FREQUENCY_FOR_RECOMMENDATION;
475
+ this.#mediumFrequencyThreshold = options?.mediumFrequencyThreshold ?? DEFAULT_MEDIUM_FREQUENCY_THRESHOLD;
476
+ this.#highFrequencyThreshold = options?.highFrequencyThreshold ?? DEFAULT_HIGH_FREQUENCY_THRESHOLD;
477
+ }
478
+ /**
479
+ * Records a query execution from its AST.
480
+ *
481
+ * This is called automatically when attached to a store.
482
+ * Can also be called manually for custom integrations.
483
+ *
484
+ * @param ast - The query AST to analyze
485
+ */
486
+ recordQuery(ast) {
487
+ this.#collector.recordQuery();
488
+ const accesses = extractPropertyAccesses(ast);
489
+ for (const access of accesses) {
490
+ const paths = resolveAccessPaths(access, this.#schemaIntrospector);
491
+ for (const path of paths) {
492
+ this.#collector.record(path, access.context, access.predicateType);
493
+ }
494
+ }
495
+ }
496
+ /**
497
+ * Attaches the profiler to a store.
498
+ *
499
+ * Returns a wrapped store that tracks all query executions.
500
+ * The wrapped store behaves identically to the original.
501
+ *
502
+ * @param store - The store to attach to
503
+ * @returns A profiled store with the `profiler` property
504
+ * @throws Error if the profiler is already attached to another store
505
+ */
506
+ attachToStore(store) {
507
+ if (this.#attached) {
508
+ throw new Error(
509
+ "Profiler is already attached. Call detach() first or create a new profiler."
510
+ );
511
+ }
512
+ this.#attached = true;
513
+ this.#schemaIntrospector = chunkP5CNM325_cjs.createSchemaIntrospector(
514
+ buildNodeSchemaMap(store.graph),
515
+ buildEdgeSchemaMap(store.graph)
516
+ );
517
+ return createProfiledStore(store, this);
518
+ }
519
+ /**
520
+ * Generates a complete profile report.
521
+ *
522
+ * The report includes:
523
+ * - All property access patterns with frequency and context
524
+ * - Index recommendations sorted by priority
525
+ * - List of unindexed filter properties
526
+ * - Summary statistics
527
+ *
528
+ * @returns The profile report
529
+ */
530
+ getReport() {
531
+ const patterns = this.#collector.getPatterns();
532
+ const summary = this.#collector.getSummary();
533
+ return {
534
+ patterns,
535
+ recommendations: generateRecommendations(
536
+ patterns,
537
+ this.#declaredIndexes,
538
+ {
539
+ minFrequencyForRecommendation: this.#minFrequencyForRecommendation,
540
+ mediumFrequencyThreshold: this.#mediumFrequencyThreshold,
541
+ highFrequencyThreshold: this.#highFrequencyThreshold
542
+ }
543
+ ),
544
+ summary,
545
+ unindexedFilters: getUnindexedFilters(patterns, this.#declaredIndexes)
546
+ };
547
+ }
548
+ /**
549
+ * Checks if all filtered properties are covered by indexes.
550
+ *
551
+ * Throws an error if any filtered properties lack indexes.
552
+ * Useful for test assertions to ensure query performance.
553
+ *
554
+ * @throws Error if unindexed filter properties are found
555
+ *
556
+ * @example
557
+ * ```typescript
558
+ * // In your test file
559
+ * it("all filtered properties should be indexed", () => {
560
+ * profiler.assertIndexCoverage();
561
+ * });
562
+ * ```
563
+ */
564
+ assertIndexCoverage() {
565
+ const report = this.getReport();
566
+ if (report.unindexedFilters.length > 0) {
567
+ const missing = report.unindexedFilters.map((p) => formatPropertyPath(p)).join(", ");
568
+ throw new Error(`Unindexed filter properties: ${missing}`);
569
+ }
570
+ }
571
+ /**
572
+ * Resets all collected data.
573
+ *
574
+ * Clears patterns and query count. Useful for starting a fresh
575
+ * profiling session without creating a new profiler instance.
576
+ */
577
+ reset() {
578
+ this.#collector.reset();
579
+ }
580
+ /**
581
+ * Marks the profiler as detached.
582
+ *
583
+ * Call this when you're done profiling to allow reattachment
584
+ * to the same or a different store.
585
+ */
586
+ detach() {
587
+ this.#attached = false;
588
+ this.#schemaIntrospector = void 0;
589
+ }
590
+ /**
591
+ * Whether the profiler is currently attached to a store.
592
+ */
593
+ get isAttached() {
594
+ return this.#attached;
595
+ }
596
+ };
597
+ function buildNodeSchemaMap(graph) {
598
+ const entries = Object.values(graph.nodes).map(
599
+ (n) => [n.type.name, { schema: n.type.schema }]
600
+ );
601
+ return new Map(entries);
602
+ }
603
+ function buildEdgeSchemaMap(graph) {
604
+ const entries = Object.values(graph.edges).map(
605
+ (edgeRegistration) => [
606
+ edgeRegistration.type.name,
607
+ { schema: edgeRegistration.type.schema }
608
+ ]
609
+ );
610
+ return new Map(entries);
611
+ }
612
+ function resolveAccessPaths(access, schemaIntrospector) {
613
+ const kinds = access.kindNames;
614
+ if (kinds.length === 0) {
615
+ warnInDevelopment(
616
+ `[QueryProfiler] Access pattern has empty kindNames for context "${access.context}". This may indicate a bug in alias resolution.`
617
+ );
618
+ return [];
619
+ }
620
+ if (!schemaIntrospector) {
621
+ return [
622
+ {
623
+ entityType: access.entityType,
624
+ kind: kinds[0],
625
+ target: access.target
626
+ }
627
+ ];
628
+ }
629
+ if (access.target.__type !== "prop") {
630
+ return [
631
+ {
632
+ entityType: access.entityType,
633
+ kind: kinds[0],
634
+ target: access.target
635
+ }
636
+ ];
637
+ }
638
+ const pointer = access.target.pointer;
639
+ const matchingKinds = kinds.filter(
640
+ (kindName) => hasPointerInSchema(
641
+ schemaIntrospector,
642
+ access.entityType,
643
+ kindName,
644
+ pointer
645
+ )
646
+ );
647
+ const kindsToUse = matchingKinds.length > 0 ? matchingKinds : [kinds[0]];
648
+ return kindsToUse.map((kindName) => ({
649
+ entityType: access.entityType,
650
+ kind: kindName,
651
+ target: access.target
652
+ }));
653
+ }
654
+ function warnInDevelopment(message, details) {
655
+ if (!isDevelopmentEnvironment()) {
656
+ return;
657
+ }
658
+ if (details !== void 0) {
659
+ console.warn(message, details);
660
+ return;
661
+ }
662
+ console.warn(message);
663
+ }
664
+ function isDevelopmentEnvironment() {
665
+ return getNodeEnv() !== "production";
666
+ }
667
+ function getNodeEnv() {
668
+ if (typeof process === "undefined") {
669
+ return void 0;
670
+ }
671
+ return process.env.NODE_ENV;
672
+ }
673
+ function hasPointerInSchema(schemaIntrospector, entityType, kindName, pointer) {
674
+ const segments = chunkP5CNM325_cjs.parseJsonPointer(pointer);
675
+ const [first, ...rest] = segments;
676
+ if (!first) {
677
+ return false;
678
+ }
679
+ const rootInfo = entityType === "node" ? schemaIntrospector.getFieldTypeInfo(kindName, first) : schemaIntrospector.getEdgeFieldTypeInfo(kindName, first);
680
+ if (!rootInfo) {
681
+ return false;
682
+ }
683
+ if (rest.length === 0) {
684
+ return true;
685
+ }
686
+ try {
687
+ const resolved = chunkP5CNM325_cjs.resolveFieldTypeInfoAtJsonPointer(
688
+ rootInfo,
689
+ chunkP5CNM325_cjs.jsonPointer(rest)
690
+ );
691
+ return resolved !== void 0;
692
+ } catch (error) {
693
+ warnInDevelopment(
694
+ `[QueryProfiler] Failed to resolve pointer "${pointer}" for ${entityType} kind "${kindName}".`,
695
+ error
696
+ );
697
+ return false;
698
+ }
699
+ }
700
+ function formatPropertyPath(path) {
701
+ const target = path.target.__type === "prop" ? path.target.pointer : `$${path.target.field}`;
702
+ return `${path.entityType}:${path.kind}:${target}`;
703
+ }
704
+ function createProfiledStore(store, profiler) {
705
+ const handler = {
706
+ get(target, property, _receiver) {
707
+ if (property === "profiler") {
708
+ return profiler;
709
+ }
710
+ if (property === "query") {
711
+ return () => {
712
+ const builder = target.query();
713
+ return wrapQueryBuilder(builder, profiler);
714
+ };
715
+ }
716
+ const value = Reflect.get(target, property, target);
717
+ if (typeof value === "function") {
718
+ return value.bind(target);
719
+ }
720
+ return value;
721
+ }
722
+ };
723
+ return new Proxy(store, handler);
724
+ }
725
+ function wrapQueryBuilder(builder, profiler) {
726
+ const handler = {
727
+ get(target, property, receiver) {
728
+ const value = Reflect.get(target, property, receiver);
729
+ if (typeof value !== "function") {
730
+ return value;
731
+ }
732
+ return (...args) => {
733
+ const result = value.apply(target, args);
734
+ if (result === null || typeof result !== "object") {
735
+ return result;
736
+ }
737
+ if (hasExecutableQueryShape(result)) {
738
+ return wrapExecutableQuery(result, profiler);
739
+ }
740
+ if (hasQueryBuilderShape(result)) {
741
+ return wrapQueryBuilder(result, profiler);
742
+ }
743
+ return result;
744
+ };
745
+ }
746
+ };
747
+ return new Proxy(builder, handler);
748
+ }
749
+ function wrapExecutableQuery(query, profiler) {
750
+ const handler = {
751
+ get(target, property, receiver) {
752
+ const value = Reflect.get(target, property, receiver);
753
+ if (typeof value !== "function") {
754
+ return value;
755
+ }
756
+ const fn = value;
757
+ return (...args) => {
758
+ if (property === "execute" || property === "paginate" || property === "stream") {
759
+ const toAst = Reflect.get(
760
+ target,
761
+ "toAst",
762
+ receiver
763
+ );
764
+ const ast = toAst.call(target);
765
+ profiler.recordQuery(ast);
766
+ }
767
+ const result = fn.apply(target, args);
768
+ if (result && typeof result === "object" && hasExecutableQueryShape(result)) {
769
+ return wrapExecutableQuery(result, profiler);
770
+ }
771
+ return result;
772
+ };
773
+ }
774
+ };
775
+ return new Proxy(query, handler);
776
+ }
777
+ function hasExecutableQueryShape(object) {
778
+ return "toAst" in object && typeof object.toAst === "function" && "execute" in object && typeof object.execute === "function";
779
+ }
780
+ function hasQueryBuilderShape(object) {
781
+ return "from" in object && typeof object.from === "function" || "select" in object && typeof object.select === "function" || "whereNode" in object && typeof object.whereNode === "function" || "traverse" in object && typeof object.traverse === "function" || // Traversal builder methods
782
+ "whereEdge" in object && typeof object.whereEdge === "function" || "to" in object && typeof object.to === "function";
783
+ }
784
+
785
+ exports.ProfileCollector = ProfileCollector;
786
+ exports.QueryProfiler = QueryProfiler;
787
+ exports.extractPropertyAccesses = extractPropertyAccesses;
788
+ exports.generateRecommendations = generateRecommendations;
789
+ exports.getUnindexedFilters = getUnindexedFilters;
790
+ exports.keyToPath = keyToPath;
791
+ exports.pathToKey = pathToKey;
792
+ //# sourceMappingURL=index.cjs.map
793
+ //# sourceMappingURL=index.cjs.map