@topgunbuild/core 0.9.0 → 0.10.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.
package/dist/index.js CHANGED
@@ -37,6 +37,24 @@ __export(index_exports, {
37
37
  BuiltInResolvers: () => BuiltInResolvers,
38
38
  ClientOpMessageSchema: () => ClientOpMessageSchema,
39
39
  ClientOpSchema: () => ClientOpSchema,
40
+ ClusterSearchReqMessageSchema: () => ClusterSearchReqMessageSchema,
41
+ ClusterSearchReqPayloadSchema: () => ClusterSearchReqPayloadSchema,
42
+ ClusterSearchRespMessageSchema: () => ClusterSearchRespMessageSchema,
43
+ ClusterSearchRespPayloadSchema: () => ClusterSearchRespPayloadSchema,
44
+ ClusterSearchSubscribeMessageSchema: () => ClusterSearchSubscribeMessageSchema,
45
+ ClusterSearchSubscribePayloadSchema: () => ClusterSearchSubscribePayloadSchema,
46
+ ClusterSearchUnsubscribeMessageSchema: () => ClusterSearchUnsubscribeMessageSchema,
47
+ ClusterSearchUnsubscribePayloadSchema: () => ClusterSearchUnsubscribePayloadSchema,
48
+ ClusterSearchUpdateMessageSchema: () => ClusterSearchUpdateMessageSchema,
49
+ ClusterSearchUpdatePayloadSchema: () => ClusterSearchUpdatePayloadSchema,
50
+ ClusterSubAckMessageSchema: () => ClusterSubAckMessageSchema,
51
+ ClusterSubAckPayloadSchema: () => ClusterSubAckPayloadSchema,
52
+ ClusterSubRegisterMessageSchema: () => ClusterSubRegisterMessageSchema,
53
+ ClusterSubRegisterPayloadSchema: () => ClusterSubRegisterPayloadSchema,
54
+ ClusterSubUnregisterMessageSchema: () => ClusterSubUnregisterMessageSchema,
55
+ ClusterSubUnregisterPayloadSchema: () => ClusterSubUnregisterPayloadSchema,
56
+ ClusterSubUpdateMessageSchema: () => ClusterSubUpdateMessageSchema,
57
+ ClusterSubUpdatePayloadSchema: () => ClusterSubUpdatePayloadSchema,
40
58
  ConflictResolverDefSchema: () => ConflictResolverDefSchema,
41
59
  ConflictResolverSchema: () => ConflictResolverSchema,
42
60
  ConsistencyLevel: () => ConsistencyLevel,
@@ -44,13 +62,16 @@ __export(index_exports, {
44
62
  CounterResponseSchema: () => CounterResponseSchema,
45
63
  CounterSyncSchema: () => CounterSyncSchema,
46
64
  CounterUpdateSchema: () => CounterUpdateSchema,
65
+ CursorStatusSchema: () => CursorStatusSchema,
47
66
  DEFAULT_BACKUP_COUNT: () => DEFAULT_BACKUP_COUNT,
48
67
  DEFAULT_CIRCUIT_BREAKER_CONFIG: () => DEFAULT_CIRCUIT_BREAKER_CONFIG,
49
68
  DEFAULT_CONNECTION_POOL_CONFIG: () => DEFAULT_CONNECTION_POOL_CONFIG,
69
+ DEFAULT_CURSOR_MAX_AGE_MS: () => DEFAULT_CURSOR_MAX_AGE_MS,
50
70
  DEFAULT_EVENT_JOURNAL_CONFIG: () => DEFAULT_EVENT_JOURNAL_CONFIG,
51
71
  DEFAULT_MIGRATION_CONFIG: () => DEFAULT_MIGRATION_CONFIG,
52
72
  DEFAULT_PARTITION_ROUTER_CONFIG: () => DEFAULT_PARTITION_ROUTER_CONFIG,
53
73
  DEFAULT_PROCESSOR_RATE_LIMITS: () => DEFAULT_PROCESSOR_RATE_LIMITS,
74
+ DEFAULT_QUERY_CURSOR_MAX_AGE_MS: () => DEFAULT_QUERY_CURSOR_MAX_AGE_MS,
54
75
  DEFAULT_REPLICATION_CONFIG: () => DEFAULT_REPLICATION_CONFIG,
55
76
  DEFAULT_RESOLVER_RATE_LIMITS: () => DEFAULT_RESOLVER_RATE_LIMITS,
56
77
  DEFAULT_STOP_WORDS: () => DEFAULT_STOP_WORDS,
@@ -128,7 +149,10 @@ __export(index_exports, {
128
149
  PredicateNodeSchema: () => PredicateNodeSchema,
129
150
  PredicateOpSchema: () => PredicateOpSchema,
130
151
  Predicates: () => Predicates,
152
+ QueryCursor: () => QueryCursor,
131
153
  QueryOptimizer: () => QueryOptimizer,
154
+ QueryRespMessageSchema: () => QueryRespMessageSchema,
155
+ QueryRespPayloadSchema: () => QueryRespPayloadSchema,
132
156
  QuerySchema: () => QuerySchema,
133
157
  QuerySubMessageSchema: () => QuerySubMessageSchema,
134
158
  QueryUnsubMessageSchema: () => QueryUnsubMessageSchema,
@@ -137,6 +161,7 @@ __export(index_exports, {
137
161
  RegisterResolverRequestSchema: () => RegisterResolverRequestSchema,
138
162
  RegisterResolverResponseSchema: () => RegisterResolverResponseSchema,
139
163
  Ringbuffer: () => Ringbuffer,
164
+ SearchCursor: () => SearchCursor,
140
165
  SearchMessageSchema: () => SearchMessageSchema,
141
166
  SearchOptionsSchema: () => SearchOptionsSchema,
142
167
  SearchPayloadSchema: () => SearchPayloadSchema,
@@ -179,15 +204,19 @@ __export(index_exports, {
179
204
  combineHashes: () => combineHashes,
180
205
  compareHLCTimestamps: () => compareHLCTimestamps,
181
206
  compareTimestamps: () => compareTimestamps,
207
+ compareValues: () => compareValues,
182
208
  createFieldComparator: () => createFieldComparator,
183
209
  createPredicateMatcher: () => createPredicateMatcher,
210
+ decodeBase64Url: () => decodeBase64Url,
184
211
  deepMerge: () => deepMerge,
185
212
  deserialize: () => deserialize,
186
213
  disableNativeHash: () => disableNativeHash,
214
+ encodeBase64Url: () => encodeBase64Url,
187
215
  evaluatePredicate: () => evaluatePredicate,
188
216
  getHighestWriteConcernLevel: () => getHighestWriteConcernLevel,
189
217
  hashORMapEntry: () => hashORMapEntry,
190
218
  hashORMapRecord: () => hashORMapRecord,
219
+ hashObject: () => hashObject,
191
220
  hashString: () => hashString,
192
221
  isLogicalQuery: () => isLogicalQuery,
193
222
  isSimpleQuery: () => isSimpleQuery,
@@ -337,6 +366,18 @@ function resetNativeHash() {
337
366
  nativeHash = null;
338
367
  nativeLoadAttempted = false;
339
368
  }
369
+ function hashObject(obj) {
370
+ const json = JSON.stringify(obj, (_, value) => {
371
+ if (value && typeof value === "object" && !Array.isArray(value)) {
372
+ return Object.keys(value).sort().reduce((sorted, key) => {
373
+ sorted[key] = value[key];
374
+ return sorted;
375
+ }, {});
376
+ }
377
+ return value;
378
+ });
379
+ return hashString(json);
380
+ }
340
381
 
341
382
  // src/MerkleTree.ts
342
383
  var MerkleTree = class {
@@ -2365,7 +2406,24 @@ var QuerySchema = import_zod3.z.object({
2365
2406
  predicate: PredicateNodeSchema.optional(),
2366
2407
  sort: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.enum(["asc", "desc"])).optional(),
2367
2408
  limit: import_zod3.z.number().optional(),
2368
- offset: import_zod3.z.number().optional()
2409
+ cursor: import_zod3.z.string().optional()
2410
+ // Phase 14.1: replaces offset
2411
+ });
2412
+ var CursorStatusSchema = import_zod3.z.enum(["valid", "expired", "invalid", "none"]);
2413
+ var QueryRespPayloadSchema = import_zod3.z.object({
2414
+ queryId: import_zod3.z.string(),
2415
+ results: import_zod3.z.array(import_zod3.z.object({
2416
+ key: import_zod3.z.string(),
2417
+ value: import_zod3.z.unknown()
2418
+ })),
2419
+ nextCursor: import_zod3.z.string().optional(),
2420
+ hasMore: import_zod3.z.boolean().optional(),
2421
+ /** Debug info: status of input cursor processing */
2422
+ cursorStatus: CursorStatusSchema.optional()
2423
+ });
2424
+ var QueryRespMessageSchema = import_zod3.z.object({
2425
+ type: import_zod3.z.literal("QUERY_RESP"),
2426
+ payload: QueryRespPayloadSchema
2369
2427
  });
2370
2428
  var ClientOpSchema = import_zod3.z.object({
2371
2429
  id: import_zod3.z.string().optional(),
@@ -2845,6 +2903,87 @@ var OpRejectedMessageSchema = import_zod3.z.object({
2845
2903
  code: import_zod3.z.number().optional()
2846
2904
  })
2847
2905
  });
2906
+ var ClusterSubRegisterPayloadSchema = import_zod3.z.object({
2907
+ /** Unique subscription ID */
2908
+ subscriptionId: import_zod3.z.string(),
2909
+ /** Node ID of the coordinator (receives updates) */
2910
+ coordinatorNodeId: import_zod3.z.string(),
2911
+ /** Map name to subscribe to */
2912
+ mapName: import_zod3.z.string(),
2913
+ /** Subscription type */
2914
+ type: import_zod3.z.enum(["SEARCH", "QUERY"]),
2915
+ // For FTS subscriptions
2916
+ /** Search query string (for SEARCH type) */
2917
+ searchQuery: import_zod3.z.string().optional(),
2918
+ /** Search options (for SEARCH type) */
2919
+ searchOptions: import_zod3.z.object({
2920
+ limit: import_zod3.z.number().int().positive().optional(),
2921
+ minScore: import_zod3.z.number().optional(),
2922
+ boost: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.number()).optional()
2923
+ }).optional(),
2924
+ // For Query subscriptions
2925
+ /** Query predicate (for QUERY type) */
2926
+ queryPredicate: import_zod3.z.any().optional(),
2927
+ /** Query sort order (for QUERY type) */
2928
+ querySort: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.enum(["asc", "desc"])).optional()
2929
+ });
2930
+ var ClusterSubRegisterMessageSchema = import_zod3.z.object({
2931
+ type: import_zod3.z.literal("CLUSTER_SUB_REGISTER"),
2932
+ payload: ClusterSubRegisterPayloadSchema
2933
+ });
2934
+ var ClusterSubAckPayloadSchema = import_zod3.z.object({
2935
+ /** Correlates to subscriptionId */
2936
+ subscriptionId: import_zod3.z.string(),
2937
+ /** Node that registered this subscription */
2938
+ nodeId: import_zod3.z.string(),
2939
+ /** Registration success status */
2940
+ success: import_zod3.z.boolean(),
2941
+ /** Error message if failed */
2942
+ error: import_zod3.z.string().optional(),
2943
+ /** Initial results from this node */
2944
+ initialResults: import_zod3.z.array(import_zod3.z.object({
2945
+ key: import_zod3.z.string(),
2946
+ value: import_zod3.z.unknown(),
2947
+ score: import_zod3.z.number().optional(),
2948
+ matchedTerms: import_zod3.z.array(import_zod3.z.string()).optional()
2949
+ })).optional(),
2950
+ /** Total matching documents on this node */
2951
+ totalHits: import_zod3.z.number().int().nonnegative().optional()
2952
+ });
2953
+ var ClusterSubAckMessageSchema = import_zod3.z.object({
2954
+ type: import_zod3.z.literal("CLUSTER_SUB_ACK"),
2955
+ payload: ClusterSubAckPayloadSchema
2956
+ });
2957
+ var ClusterSubUpdatePayloadSchema = import_zod3.z.object({
2958
+ /** Subscription ID this update belongs to */
2959
+ subscriptionId: import_zod3.z.string(),
2960
+ /** Node that produced this update */
2961
+ sourceNodeId: import_zod3.z.string(),
2962
+ /** Document key */
2963
+ key: import_zod3.z.string(),
2964
+ /** Document value (null for LEAVE) */
2965
+ value: import_zod3.z.unknown(),
2966
+ /** Search score (for SEARCH type) */
2967
+ score: import_zod3.z.number().optional(),
2968
+ /** Matched terms (for SEARCH type) */
2969
+ matchedTerms: import_zod3.z.array(import_zod3.z.string()).optional(),
2970
+ /** Change type: ENTER (new match), UPDATE (still matches), LEAVE (no longer matches) */
2971
+ changeType: import_zod3.z.enum(["ENTER", "UPDATE", "LEAVE"]),
2972
+ /** Timestamp for ordering */
2973
+ timestamp: import_zod3.z.number()
2974
+ });
2975
+ var ClusterSubUpdateMessageSchema = import_zod3.z.object({
2976
+ type: import_zod3.z.literal("CLUSTER_SUB_UPDATE"),
2977
+ payload: ClusterSubUpdatePayloadSchema
2978
+ });
2979
+ var ClusterSubUnregisterPayloadSchema = import_zod3.z.object({
2980
+ /** Subscription ID to remove */
2981
+ subscriptionId: import_zod3.z.string()
2982
+ });
2983
+ var ClusterSubUnregisterMessageSchema = import_zod3.z.object({
2984
+ type: import_zod3.z.literal("CLUSTER_SUB_UNREGISTER"),
2985
+ payload: ClusterSubUnregisterPayloadSchema
2986
+ });
2848
2987
  var MessageSchema = import_zod3.z.discriminatedUnion("type", [
2849
2988
  AuthMessageSchema,
2850
2989
  QuerySubMessageSchema,
@@ -2902,8 +3041,95 @@ var MessageSchema = import_zod3.z.discriminatedUnion("type", [
2902
3041
  // Phase 11.1b: Live Search Subscriptions
2903
3042
  SearchSubMessageSchema,
2904
3043
  SearchUpdateMessageSchema,
2905
- SearchUnsubMessageSchema
3044
+ SearchUnsubMessageSchema,
3045
+ // Phase 14.2: Distributed Live Subscriptions (cluster-internal)
3046
+ ClusterSubRegisterMessageSchema,
3047
+ ClusterSubAckMessageSchema,
3048
+ ClusterSubUpdateMessageSchema,
3049
+ ClusterSubUnregisterMessageSchema
2906
3050
  ]);
3051
+ var ClusterSearchReqPayloadSchema = import_zod3.z.object({
3052
+ /** Unique request ID for correlation */
3053
+ requestId: import_zod3.z.string(),
3054
+ /** Map name to search */
3055
+ mapName: import_zod3.z.string(),
3056
+ /** Search query text */
3057
+ query: import_zod3.z.string(),
3058
+ /** Search options */
3059
+ options: import_zod3.z.object({
3060
+ /** Maximum results per node (for Top-K) */
3061
+ limit: import_zod3.z.number().int().positive().max(1e3),
3062
+ /** Minimum score threshold */
3063
+ minScore: import_zod3.z.number().optional(),
3064
+ /** Fields to boost */
3065
+ boost: import_zod3.z.record(import_zod3.z.string(), import_zod3.z.number()).optional(),
3066
+ /** Include matched terms in response */
3067
+ includeMatchedTerms: import_zod3.z.boolean().optional(),
3068
+ /** For cursor-based pagination: return results after this score */
3069
+ afterScore: import_zod3.z.number().optional(),
3070
+ /** For cursor-based pagination: return results after this key (tie-breaking) */
3071
+ afterKey: import_zod3.z.string().optional()
3072
+ }),
3073
+ /** Timeout in milliseconds */
3074
+ timeoutMs: import_zod3.z.number().int().positive().optional()
3075
+ });
3076
+ var ClusterSearchReqMessageSchema = import_zod3.z.object({
3077
+ type: import_zod3.z.literal("CLUSTER_SEARCH_REQ"),
3078
+ payload: ClusterSearchReqPayloadSchema
3079
+ });
3080
+ var ClusterSearchRespPayloadSchema = import_zod3.z.object({
3081
+ /** Correlates to requestId */
3082
+ requestId: import_zod3.z.string(),
3083
+ /** Node that produced these results */
3084
+ nodeId: import_zod3.z.string(),
3085
+ /** Search results with scores */
3086
+ results: import_zod3.z.array(import_zod3.z.object({
3087
+ key: import_zod3.z.string(),
3088
+ value: import_zod3.z.unknown(),
3089
+ score: import_zod3.z.number(),
3090
+ matchedTerms: import_zod3.z.array(import_zod3.z.string()).optional()
3091
+ })),
3092
+ /** Total matching documents on this node (for pagination) */
3093
+ totalHits: import_zod3.z.number().int().nonnegative(),
3094
+ /** Execution time on this node in ms (for monitoring) */
3095
+ executionTimeMs: import_zod3.z.number().nonnegative(),
3096
+ /** Error if search failed on this node */
3097
+ error: import_zod3.z.string().optional()
3098
+ });
3099
+ var ClusterSearchRespMessageSchema = import_zod3.z.object({
3100
+ type: import_zod3.z.literal("CLUSTER_SEARCH_RESP"),
3101
+ payload: ClusterSearchRespPayloadSchema
3102
+ });
3103
+ var ClusterSearchSubscribePayloadSchema = import_zod3.z.object({
3104
+ subscriptionId: import_zod3.z.string(),
3105
+ mapName: import_zod3.z.string(),
3106
+ query: import_zod3.z.string(),
3107
+ options: SearchOptionsSchema.optional()
3108
+ });
3109
+ var ClusterSearchSubscribeMessageSchema = import_zod3.z.object({
3110
+ type: import_zod3.z.literal("CLUSTER_SEARCH_SUBSCRIBE"),
3111
+ payload: ClusterSearchSubscribePayloadSchema
3112
+ });
3113
+ var ClusterSearchUnsubscribePayloadSchema = import_zod3.z.object({
3114
+ subscriptionId: import_zod3.z.string()
3115
+ });
3116
+ var ClusterSearchUnsubscribeMessageSchema = import_zod3.z.object({
3117
+ type: import_zod3.z.literal("CLUSTER_SEARCH_UNSUBSCRIBE"),
3118
+ payload: ClusterSearchUnsubscribePayloadSchema
3119
+ });
3120
+ var ClusterSearchUpdatePayloadSchema = import_zod3.z.object({
3121
+ subscriptionId: import_zod3.z.string(),
3122
+ nodeId: import_zod3.z.string(),
3123
+ key: import_zod3.z.string(),
3124
+ value: import_zod3.z.unknown(),
3125
+ score: import_zod3.z.number(),
3126
+ matchedTerms: import_zod3.z.array(import_zod3.z.string()).optional(),
3127
+ type: SearchUpdateTypeSchema
3128
+ });
3129
+ var ClusterSearchUpdateMessageSchema = import_zod3.z.object({
3130
+ type: import_zod3.z.literal("CLUSTER_SEARCH_UPDATE"),
3131
+ payload: ClusterSearchUpdatePayloadSchema
3132
+ });
2907
3133
 
2908
3134
  // src/types/WriteConcern.ts
2909
3135
  var WriteConcern = /* @__PURE__ */ ((WriteConcern2) => {
@@ -6228,7 +6454,7 @@ var QueryOptimizer = class {
6228
6454
  */
6229
6455
  optimizeWithOptions(query, options) {
6230
6456
  const basePlan = this.optimize(query);
6231
- if (!options.sort && options.limit === void 0 && options.offset === void 0) {
6457
+ if (!options.sort && options.limit === void 0 && options.cursor === void 0) {
6232
6458
  return basePlan;
6233
6459
  }
6234
6460
  let indexedSort = false;
@@ -6250,7 +6476,8 @@ var QueryOptimizer = class {
6250
6476
  indexedSort,
6251
6477
  sort: sortField && sortDirection ? { field: sortField, direction: sortDirection } : void 0,
6252
6478
  limit: options.limit,
6253
- offset: options.offset
6479
+ cursor: options.cursor
6480
+ // Phase 14.1: replaces offset
6254
6481
  };
6255
6482
  }
6256
6483
  /**
@@ -8370,6 +8597,264 @@ var ReciprocalRankFusion = class {
8370
8597
  }
8371
8598
  };
8372
8599
 
8600
+ // src/utils/base64url.ts
8601
+ function encodeBase64Url(str) {
8602
+ if (typeof Buffer !== "undefined") {
8603
+ return Buffer.from(str, "utf8").toString("base64url");
8604
+ }
8605
+ const base64 = btoa(unescape(encodeURIComponent(str)));
8606
+ return base64.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
8607
+ }
8608
+ function decodeBase64Url(encoded) {
8609
+ if (typeof Buffer !== "undefined") {
8610
+ return Buffer.from(encoded, "base64url").toString("utf8");
8611
+ }
8612
+ let base64 = encoded.replace(/-/g, "+").replace(/_/g, "/");
8613
+ const pad = base64.length % 4;
8614
+ if (pad) {
8615
+ base64 += "=".repeat(4 - pad);
8616
+ }
8617
+ return decodeURIComponent(escape(atob(base64)));
8618
+ }
8619
+
8620
+ // src/utils/compare.ts
8621
+ function compareValues(a, b) {
8622
+ if (a == null && b == null) return 0;
8623
+ if (a == null) return -1;
8624
+ if (b == null) return 1;
8625
+ if (typeof a === "number" && typeof b === "number") {
8626
+ return a - b;
8627
+ }
8628
+ if (a instanceof Date && b instanceof Date) {
8629
+ return a.getTime() - b.getTime();
8630
+ }
8631
+ if (typeof a === "string" && typeof b === "string") {
8632
+ const dateA = Date.parse(a);
8633
+ const dateB = Date.parse(b);
8634
+ if (!isNaN(dateA) && !isNaN(dateB)) {
8635
+ return dateA - dateB;
8636
+ }
8637
+ return a.localeCompare(b);
8638
+ }
8639
+ if (typeof a === "boolean" && typeof b === "boolean") {
8640
+ return a === b ? 0 : a ? 1 : -1;
8641
+ }
8642
+ return String(a).localeCompare(String(b));
8643
+ }
8644
+
8645
+ // src/query/QueryCursor.ts
8646
+ var DEFAULT_QUERY_CURSOR_MAX_AGE_MS = 10 * 60 * 1e3;
8647
+ var QueryCursor = class {
8648
+ /**
8649
+ * Encode cursor data to an opaque base64url string.
8650
+ *
8651
+ * @param data - Cursor data to encode
8652
+ * @returns Opaque cursor string
8653
+ */
8654
+ static encode(data) {
8655
+ const json = JSON.stringify(data);
8656
+ return encodeBase64Url(json);
8657
+ }
8658
+ /**
8659
+ * Decode cursor string back to data.
8660
+ *
8661
+ * @param cursor - Opaque cursor string
8662
+ * @returns Decoded cursor data, or null if invalid
8663
+ */
8664
+ static decode(cursor) {
8665
+ try {
8666
+ const json = decodeBase64Url(cursor);
8667
+ const data = JSON.parse(json);
8668
+ if (typeof data !== "object" || typeof data.nodeValues !== "object" || typeof data.nodeKeys !== "object" || typeof data.sortField !== "string" || data.sortDirection !== "asc" && data.sortDirection !== "desc" || typeof data.predicateHash !== "number" || typeof data.sortHash !== "number" || typeof data.timestamp !== "number") {
8669
+ return null;
8670
+ }
8671
+ return data;
8672
+ } catch {
8673
+ return null;
8674
+ }
8675
+ }
8676
+ /**
8677
+ * Create a cursor from query results.
8678
+ *
8679
+ * The cursor captures the last seen position for each node that contributed
8680
+ * results, enabling efficient resumption in the next page request.
8681
+ *
8682
+ * @param results - Array of results with sort values and optional node tracking
8683
+ * @param sort - Sort configuration (single field only for v1)
8684
+ * @param predicate - Query predicate (for validation)
8685
+ * @returns Encoded cursor string
8686
+ */
8687
+ static fromResults(results, sort, predicate) {
8688
+ const nodeValues = {};
8689
+ const nodeKeys = {};
8690
+ const sortEntries = Object.entries(sort);
8691
+ if (sortEntries.length === 0) {
8692
+ throw new Error("Sort configuration required for cursor pagination");
8693
+ }
8694
+ const [sortField, sortDirection] = sortEntries[0];
8695
+ for (const result of results) {
8696
+ const nodeId = result.nodeId ?? "local";
8697
+ nodeValues[nodeId] = result.sortValue;
8698
+ nodeKeys[nodeId] = result.key;
8699
+ }
8700
+ const data = {
8701
+ nodeValues,
8702
+ nodeKeys,
8703
+ sortField,
8704
+ sortDirection,
8705
+ predicateHash: hashObject(predicate ?? null),
8706
+ sortHash: hashObject(sort),
8707
+ timestamp: Date.now()
8708
+ };
8709
+ return this.encode(data);
8710
+ }
8711
+ /**
8712
+ * Create a cursor from the last result only.
8713
+ * Useful for local-only queries.
8714
+ *
8715
+ * @param lastResult - The last result in the current page
8716
+ * @param sort - Sort configuration
8717
+ * @param predicate - Query predicate
8718
+ * @returns Encoded cursor string
8719
+ */
8720
+ static fromLastResult(lastResult, sort, predicate) {
8721
+ return this.fromResults([lastResult], sort, predicate);
8722
+ }
8723
+ /**
8724
+ * Validate that a cursor is valid for the given query.
8725
+ *
8726
+ * Checks:
8727
+ * 1. Predicate hash matches (cursor was created for this query)
8728
+ * 2. Sort hash matches (sort configuration unchanged)
8729
+ * 3. Cursor is not expired
8730
+ *
8731
+ * @param cursor - Decoded cursor data
8732
+ * @param predicate - Query predicate to validate against
8733
+ * @param sort - Sort configuration to validate against
8734
+ * @param options - Validation options
8735
+ * @returns true if cursor is valid
8736
+ */
8737
+ static isValid(cursor, predicate, sort, options) {
8738
+ const maxAge = options?.maxAgeMs ?? DEFAULT_QUERY_CURSOR_MAX_AGE_MS;
8739
+ if (cursor.predicateHash !== hashObject(predicate ?? null)) {
8740
+ return false;
8741
+ }
8742
+ if (cursor.sortHash !== hashObject(sort)) {
8743
+ return false;
8744
+ }
8745
+ if (Date.now() - cursor.timestamp > maxAge) {
8746
+ return false;
8747
+ }
8748
+ return true;
8749
+ }
8750
+ /**
8751
+ * Get the cursor position for a specific node.
8752
+ *
8753
+ * @param cursor - Decoded cursor data
8754
+ * @param nodeId - Node ID to get position for (defaults to 'local')
8755
+ * @returns Position info or null if node not in cursor
8756
+ */
8757
+ static getNodePosition(cursor, nodeId = "local") {
8758
+ if (!(nodeId in cursor.nodeValues)) {
8759
+ return null;
8760
+ }
8761
+ return {
8762
+ afterValue: cursor.nodeValues[nodeId],
8763
+ afterKey: cursor.nodeKeys[nodeId]
8764
+ };
8765
+ }
8766
+ /**
8767
+ * Check if a result should be included based on cursor position.
8768
+ *
8769
+ * For ASC sort: value > cursorValue OR (value === cursorValue AND key > cursorKey)
8770
+ * For DESC sort: value < cursorValue OR (value === cursorValue AND key > cursorKey)
8771
+ *
8772
+ * @param result - Result to check
8773
+ * @param cursor - Decoded cursor data
8774
+ * @returns true if result should be included (is after cursor)
8775
+ */
8776
+ static isAfterCursor(result, cursor) {
8777
+ const nodeId = result.nodeId ?? "local";
8778
+ const position = this.getNodePosition(cursor, nodeId);
8779
+ if (!position) {
8780
+ return true;
8781
+ }
8782
+ const cmp = this.compareValues(result.sortValue, position.afterValue);
8783
+ if (cursor.sortDirection === "asc") {
8784
+ if (cmp > 0) return true;
8785
+ if (cmp === 0 && result.key > position.afterKey) return true;
8786
+ } else {
8787
+ if (cmp < 0) return true;
8788
+ if (cmp === 0 && result.key > position.afterKey) return true;
8789
+ }
8790
+ return false;
8791
+ }
8792
+ /**
8793
+ * Compare two values with type-aware comparison.
8794
+ * Delegates to shared compareValues utility.
8795
+ *
8796
+ * @param a - First value
8797
+ * @param b - Second value
8798
+ * @returns Negative if a < b, 0 if equal, positive if a > b
8799
+ */
8800
+ static compareValues(a, b) {
8801
+ return compareValues(a, b);
8802
+ }
8803
+ /**
8804
+ * Merge multiple cursors into one.
8805
+ * Useful when combining results from multiple sub-queries or nodes.
8806
+ *
8807
+ * Keeps the furthest position for each node.
8808
+ *
8809
+ * @param cursors - Array of decoded cursor data
8810
+ * @param sort - Sort configuration
8811
+ * @param predicate - Query predicate
8812
+ * @returns New merged cursor
8813
+ */
8814
+ static merge(cursors, sort, predicate) {
8815
+ const nodeValues = {};
8816
+ const nodeKeys = {};
8817
+ const sortEntries = Object.entries(sort);
8818
+ const [sortField, sortDirection] = sortEntries[0];
8819
+ for (const cursor of cursors) {
8820
+ for (const nodeId of Object.keys(cursor.nodeValues)) {
8821
+ const existingValue = nodeValues[nodeId];
8822
+ const newValue = cursor.nodeValues[nodeId];
8823
+ if (existingValue === void 0) {
8824
+ nodeValues[nodeId] = newValue;
8825
+ nodeKeys[nodeId] = cursor.nodeKeys[nodeId];
8826
+ } else {
8827
+ const cmp = this.compareValues(newValue, existingValue);
8828
+ const isFurther = sortDirection === "asc" ? cmp > 0 || cmp === 0 && cursor.nodeKeys[nodeId] > nodeKeys[nodeId] : cmp < 0 || cmp === 0 && cursor.nodeKeys[nodeId] > nodeKeys[nodeId];
8829
+ if (isFurther) {
8830
+ nodeValues[nodeId] = newValue;
8831
+ nodeKeys[nodeId] = cursor.nodeKeys[nodeId];
8832
+ }
8833
+ }
8834
+ }
8835
+ }
8836
+ return {
8837
+ nodeValues,
8838
+ nodeKeys,
8839
+ sortField,
8840
+ sortDirection,
8841
+ predicateHash: hashObject(predicate ?? null),
8842
+ sortHash: hashObject(sort),
8843
+ timestamp: Date.now()
8844
+ };
8845
+ }
8846
+ /**
8847
+ * Extract sort value from a record for cursor generation.
8848
+ *
8849
+ * @param record - Record to extract sort value from
8850
+ * @param sortField - Field to extract
8851
+ * @returns Sort value
8852
+ */
8853
+ static extractSortValue(record, sortField) {
8854
+ return record[sortField];
8855
+ }
8856
+ };
8857
+
8373
8858
  // src/IndexedLWWMap.ts
8374
8859
  var IndexedLWWMap = class extends LWWMap {
8375
8860
  constructor(hlc, options = {}) {
@@ -10945,6 +11430,179 @@ var IndexedORMap = class extends ORMap {
10945
11430
  return null;
10946
11431
  }
10947
11432
  };
11433
+
11434
+ // src/search/SearchCursor.ts
11435
+ var DEFAULT_CURSOR_MAX_AGE_MS = 5 * 60 * 1e3;
11436
+ var SearchCursor = class {
11437
+ /**
11438
+ * Encode cursor data to an opaque base64url string.
11439
+ *
11440
+ * @param data - Cursor data to encode
11441
+ * @returns Opaque cursor string
11442
+ */
11443
+ static encode(data) {
11444
+ const json = JSON.stringify(data);
11445
+ return encodeBase64Url(json);
11446
+ }
11447
+ /**
11448
+ * Decode cursor string back to data.
11449
+ *
11450
+ * @param cursor - Opaque cursor string
11451
+ * @returns Decoded cursor data, or null if invalid
11452
+ */
11453
+ static decode(cursor) {
11454
+ try {
11455
+ const json = decodeBase64Url(cursor);
11456
+ const data = JSON.parse(json);
11457
+ if (typeof data !== "object" || typeof data.nodeScores !== "object" || typeof data.nodeKeys !== "object" || typeof data.queryHash !== "number" || typeof data.timestamp !== "number") {
11458
+ return null;
11459
+ }
11460
+ return data;
11461
+ } catch {
11462
+ return null;
11463
+ }
11464
+ }
11465
+ /**
11466
+ * Create a cursor from the last results of a search.
11467
+ *
11468
+ * The cursor captures the last seen position for each node that contributed
11469
+ * results, enabling efficient resumption in the next page request.
11470
+ *
11471
+ * @param results - Array of results with node tracking
11472
+ * @param query - Original query string (for validation)
11473
+ * @returns Encoded cursor string
11474
+ */
11475
+ static fromResults(results, query) {
11476
+ const nodeScores = {};
11477
+ const nodeKeys = {};
11478
+ for (const result of results) {
11479
+ nodeScores[result.nodeId] = result.score;
11480
+ nodeKeys[result.nodeId] = result.key;
11481
+ }
11482
+ const data = {
11483
+ nodeScores,
11484
+ nodeKeys,
11485
+ queryHash: hashString(query),
11486
+ timestamp: Date.now()
11487
+ };
11488
+ return this.encode(data);
11489
+ }
11490
+ /**
11491
+ * Create a cursor from the last result only.
11492
+ * Useful when you only have the final merged result.
11493
+ *
11494
+ * @param lastResult - The last result in the current page
11495
+ * @param query - Original query string
11496
+ * @returns Encoded cursor string
11497
+ */
11498
+ static fromLastResult(lastResult, query) {
11499
+ return this.fromResults([lastResult], query);
11500
+ }
11501
+ /**
11502
+ * Generate a hash for a query string.
11503
+ * Used to validate that cursor matches the current query.
11504
+ *
11505
+ * @param query - Query string to hash
11506
+ * @returns Numeric hash
11507
+ */
11508
+ static hashQuery(query) {
11509
+ return hashString(query);
11510
+ }
11511
+ /**
11512
+ * Validate that a cursor is valid for the given query.
11513
+ *
11514
+ * Checks:
11515
+ * 1. Query hash matches (cursor was created for this query)
11516
+ * 2. Cursor is not expired
11517
+ *
11518
+ * @param cursor - Decoded cursor data
11519
+ * @param query - Query string to validate against
11520
+ * @param maxAgeMs - Maximum cursor age in milliseconds (default: 5 minutes)
11521
+ * @returns true if cursor is valid
11522
+ */
11523
+ static isValid(cursor, query, maxAgeMs = DEFAULT_CURSOR_MAX_AGE_MS) {
11524
+ if (cursor.queryHash !== hashString(query)) {
11525
+ return false;
11526
+ }
11527
+ if (Date.now() - cursor.timestamp > maxAgeMs) {
11528
+ return false;
11529
+ }
11530
+ return true;
11531
+ }
11532
+ /**
11533
+ * Get the cursor position for a specific node.
11534
+ *
11535
+ * @param cursor - Decoded cursor data
11536
+ * @param nodeId - Node ID to get position for
11537
+ * @returns Position info or null if node not in cursor
11538
+ */
11539
+ static getNodePosition(cursor, nodeId) {
11540
+ if (!(nodeId in cursor.nodeScores)) {
11541
+ return null;
11542
+ }
11543
+ return {
11544
+ afterScore: cursor.nodeScores[nodeId],
11545
+ afterKey: cursor.nodeKeys[nodeId]
11546
+ };
11547
+ }
11548
+ /**
11549
+ * Check if a result should be included based on cursor position.
11550
+ *
11551
+ * Results are ordered by score descending, then key ascending for tie-breaking.
11552
+ * A result should be included if it comes AFTER the cursor position:
11553
+ * - score < cursorScore, OR
11554
+ * - score === cursorScore AND key > cursorKey
11555
+ *
11556
+ * @param result - Result to check
11557
+ * @param cursor - Decoded cursor data
11558
+ * @returns true if result should be included (is after cursor)
11559
+ */
11560
+ static isAfterCursor(result, cursor) {
11561
+ const position = this.getNodePosition(cursor, result.nodeId);
11562
+ if (!position) {
11563
+ return true;
11564
+ }
11565
+ if (result.score < position.afterScore) {
11566
+ return true;
11567
+ }
11568
+ if (result.score === position.afterScore && result.key > position.afterKey) {
11569
+ return true;
11570
+ }
11571
+ return false;
11572
+ }
11573
+ /**
11574
+ * Merge multiple cursors into one.
11575
+ * Useful when combining results from multiple sub-queries.
11576
+ *
11577
+ * @param cursors - Array of decoded cursor data
11578
+ * @param query - Original query string
11579
+ * @returns New merged cursor
11580
+ */
11581
+ static merge(cursors, query) {
11582
+ const nodeScores = {};
11583
+ const nodeKeys = {};
11584
+ for (const cursor of cursors) {
11585
+ for (const nodeId of Object.keys(cursor.nodeScores)) {
11586
+ const existingScore = nodeScores[nodeId];
11587
+ const newScore = cursor.nodeScores[nodeId];
11588
+ if (existingScore === void 0 || newScore < existingScore) {
11589
+ nodeScores[nodeId] = newScore;
11590
+ nodeKeys[nodeId] = cursor.nodeKeys[nodeId];
11591
+ } else if (newScore === existingScore) {
11592
+ if (cursor.nodeKeys[nodeId] > nodeKeys[nodeId]) {
11593
+ nodeKeys[nodeId] = cursor.nodeKeys[nodeId];
11594
+ }
11595
+ }
11596
+ }
11597
+ }
11598
+ return {
11599
+ nodeScores,
11600
+ nodeKeys,
11601
+ queryHash: hashString(query),
11602
+ timestamp: Date.now()
11603
+ };
11604
+ }
11605
+ };
10948
11606
  // Annotate the CommonJS export names for ESM import in node:
10949
11607
  0 && (module.exports = {
10950
11608
  AuthMessageSchema,
@@ -10954,6 +11612,24 @@ var IndexedORMap = class extends ORMap {
10954
11612
  BuiltInResolvers,
10955
11613
  ClientOpMessageSchema,
10956
11614
  ClientOpSchema,
11615
+ ClusterSearchReqMessageSchema,
11616
+ ClusterSearchReqPayloadSchema,
11617
+ ClusterSearchRespMessageSchema,
11618
+ ClusterSearchRespPayloadSchema,
11619
+ ClusterSearchSubscribeMessageSchema,
11620
+ ClusterSearchSubscribePayloadSchema,
11621
+ ClusterSearchUnsubscribeMessageSchema,
11622
+ ClusterSearchUnsubscribePayloadSchema,
11623
+ ClusterSearchUpdateMessageSchema,
11624
+ ClusterSearchUpdatePayloadSchema,
11625
+ ClusterSubAckMessageSchema,
11626
+ ClusterSubAckPayloadSchema,
11627
+ ClusterSubRegisterMessageSchema,
11628
+ ClusterSubRegisterPayloadSchema,
11629
+ ClusterSubUnregisterMessageSchema,
11630
+ ClusterSubUnregisterPayloadSchema,
11631
+ ClusterSubUpdateMessageSchema,
11632
+ ClusterSubUpdatePayloadSchema,
10957
11633
  ConflictResolverDefSchema,
10958
11634
  ConflictResolverSchema,
10959
11635
  ConsistencyLevel,
@@ -10961,13 +11637,16 @@ var IndexedORMap = class extends ORMap {
10961
11637
  CounterResponseSchema,
10962
11638
  CounterSyncSchema,
10963
11639
  CounterUpdateSchema,
11640
+ CursorStatusSchema,
10964
11641
  DEFAULT_BACKUP_COUNT,
10965
11642
  DEFAULT_CIRCUIT_BREAKER_CONFIG,
10966
11643
  DEFAULT_CONNECTION_POOL_CONFIG,
11644
+ DEFAULT_CURSOR_MAX_AGE_MS,
10967
11645
  DEFAULT_EVENT_JOURNAL_CONFIG,
10968
11646
  DEFAULT_MIGRATION_CONFIG,
10969
11647
  DEFAULT_PARTITION_ROUTER_CONFIG,
10970
11648
  DEFAULT_PROCESSOR_RATE_LIMITS,
11649
+ DEFAULT_QUERY_CURSOR_MAX_AGE_MS,
10971
11650
  DEFAULT_REPLICATION_CONFIG,
10972
11651
  DEFAULT_RESOLVER_RATE_LIMITS,
10973
11652
  DEFAULT_STOP_WORDS,
@@ -11045,7 +11724,10 @@ var IndexedORMap = class extends ORMap {
11045
11724
  PredicateNodeSchema,
11046
11725
  PredicateOpSchema,
11047
11726
  Predicates,
11727
+ QueryCursor,
11048
11728
  QueryOptimizer,
11729
+ QueryRespMessageSchema,
11730
+ QueryRespPayloadSchema,
11049
11731
  QuerySchema,
11050
11732
  QuerySubMessageSchema,
11051
11733
  QueryUnsubMessageSchema,
@@ -11054,6 +11736,7 @@ var IndexedORMap = class extends ORMap {
11054
11736
  RegisterResolverRequestSchema,
11055
11737
  RegisterResolverResponseSchema,
11056
11738
  Ringbuffer,
11739
+ SearchCursor,
11057
11740
  SearchMessageSchema,
11058
11741
  SearchOptionsSchema,
11059
11742
  SearchPayloadSchema,
@@ -11096,15 +11779,19 @@ var IndexedORMap = class extends ORMap {
11096
11779
  combineHashes,
11097
11780
  compareHLCTimestamps,
11098
11781
  compareTimestamps,
11782
+ compareValues,
11099
11783
  createFieldComparator,
11100
11784
  createPredicateMatcher,
11785
+ decodeBase64Url,
11101
11786
  deepMerge,
11102
11787
  deserialize,
11103
11788
  disableNativeHash,
11789
+ encodeBase64Url,
11104
11790
  evaluatePredicate,
11105
11791
  getHighestWriteConcernLevel,
11106
11792
  hashORMapEntry,
11107
11793
  hashORMapRecord,
11794
+ hashObject,
11108
11795
  hashString,
11109
11796
  isLogicalQuery,
11110
11797
  isSimpleQuery,