@strata-sync/client 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.
package/src/query.ts ADDED
@@ -0,0 +1,224 @@
1
+ import type { IdentityMap } from "./identity-map";
2
+ import type { QueryOptions, QueryResult } from "./types";
3
+
4
+ /**
5
+ * Executes a query against an identity map
6
+ */
7
+ export function executeQuery<T extends Record<string, unknown>>(
8
+ map: IdentityMap<T>,
9
+ options: QueryOptions<T> = {}
10
+ ): QueryResult<T> {
11
+ let results = map.values();
12
+
13
+ // Filter by predicate
14
+ if (options.where) {
15
+ results = results.filter(options.where);
16
+ }
17
+
18
+ // Filter archived unless explicitly included
19
+ if (!options.includeArchived) {
20
+ results = results.filter((item) => !item.archivedAt);
21
+ }
22
+
23
+ // Get total count before pagination
24
+ const totalCount = results.length;
25
+
26
+ // Sort results
27
+ if (options.orderBy) {
28
+ results = results.sort(options.orderBy);
29
+ }
30
+
31
+ // Apply offset
32
+ if (options.offset && options.offset > 0) {
33
+ results = results.slice(options.offset);
34
+ }
35
+
36
+ // Apply limit
37
+ let hasMore = false;
38
+ if (options.limit && options.limit > 0) {
39
+ hasMore = results.length > options.limit;
40
+ results = results.slice(0, options.limit);
41
+ }
42
+
43
+ return {
44
+ data: results,
45
+ hasMore,
46
+ totalCount,
47
+ };
48
+ }
49
+
50
+ /**
51
+ * Sorts by a field name
52
+ */
53
+ export function sortBy<T>(
54
+ field: keyof T,
55
+ direction: "asc" | "desc" = "asc"
56
+ ): (a: T, b: T) => number {
57
+ return (a: T, b: T) => {
58
+ const aVal = a[field];
59
+ const bVal = b[field];
60
+
61
+ if (aVal === bVal) {
62
+ return 0;
63
+ }
64
+ if (aVal === null || aVal === undefined) {
65
+ return 1;
66
+ }
67
+ if (bVal === null || bVal === undefined) {
68
+ return -1;
69
+ }
70
+
71
+ let result: number;
72
+ if (typeof aVal === "string" && typeof bVal === "string") {
73
+ result = aVal.localeCompare(bVal);
74
+ } else if (typeof aVal === "number" && typeof bVal === "number") {
75
+ result = aVal - bVal;
76
+ } else if (aVal instanceof Date && bVal instanceof Date) {
77
+ result = aVal.getTime() - bVal.getTime();
78
+ } else {
79
+ result = String(aVal).localeCompare(String(bVal));
80
+ }
81
+
82
+ return direction === "desc" ? -result : result;
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Combines multiple sort functions
88
+ */
89
+ export function combineSorts<T>(
90
+ ...sorts: Array<(a: T, b: T) => number>
91
+ ): (a: T, b: T) => number {
92
+ return (a: T, b: T) => {
93
+ for (const sort of sorts) {
94
+ const result = sort(a, b);
95
+ if (result !== 0) {
96
+ return result;
97
+ }
98
+ }
99
+ return 0;
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Creates a filter for equality
105
+ */
106
+ export function eq<T, K extends keyof T>(
107
+ field: K,
108
+ value: T[K]
109
+ ): (item: T) => boolean {
110
+ return (item: T) => item[field] === value;
111
+ }
112
+
113
+ /**
114
+ * Creates a filter for not equal
115
+ */
116
+ export function neq<T, K extends keyof T>(
117
+ field: K,
118
+ value: T[K]
119
+ ): (item: T) => boolean {
120
+ return (item: T) => item[field] !== value;
121
+ }
122
+
123
+ /**
124
+ * Creates a filter for greater than
125
+ */
126
+ export function gt<T, K extends keyof T>(
127
+ field: K,
128
+ value: T[K]
129
+ ): (item: T) => boolean {
130
+ return (item: T) => {
131
+ const itemVal = item[field];
132
+ if (typeof itemVal === "number" && typeof value === "number") {
133
+ return itemVal > value;
134
+ }
135
+ return false;
136
+ };
137
+ }
138
+
139
+ /**
140
+ * Creates a filter for less than
141
+ */
142
+ export function lt<T, K extends keyof T>(
143
+ field: K,
144
+ value: T[K]
145
+ ): (item: T) => boolean {
146
+ return (item: T) => {
147
+ const itemVal = item[field];
148
+ if (typeof itemVal === "number" && typeof value === "number") {
149
+ return itemVal < value;
150
+ }
151
+ return false;
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Creates a filter for inclusion in a list
157
+ */
158
+ export function isIn<T, K extends keyof T>(
159
+ field: K,
160
+ values: T[K][]
161
+ ): (item: T) => boolean {
162
+ const valueSet = new Set(values);
163
+ return (item: T) => valueSet.has(item[field]);
164
+ }
165
+
166
+ /**
167
+ * Creates a filter for substring matching
168
+ */
169
+ export function contains<T>(
170
+ field: keyof T,
171
+ substring: string,
172
+ caseSensitive = false
173
+ ): (item: T) => boolean {
174
+ const searchStr = caseSensitive ? substring : substring.toLowerCase();
175
+ return (item: T) => {
176
+ const value = item[field];
177
+ if (typeof value !== "string") {
178
+ return false;
179
+ }
180
+ const compareVal = caseSensitive ? value : value.toLowerCase();
181
+ return compareVal.includes(searchStr);
182
+ };
183
+ }
184
+
185
+ /**
186
+ * Creates a filter for regex matching
187
+ */
188
+ export function matches<T>(
189
+ field: keyof T,
190
+ pattern: RegExp
191
+ ): (item: T) => boolean {
192
+ return (item: T) => {
193
+ const value = item[field];
194
+ if (typeof value !== "string") {
195
+ return false;
196
+ }
197
+ return pattern.test(value);
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Combines multiple filters with AND logic
203
+ */
204
+ export function and<T>(
205
+ ...filters: Array<(item: T) => boolean>
206
+ ): (item: T) => boolean {
207
+ return (item: T) => filters.every((f) => f(item));
208
+ }
209
+
210
+ /**
211
+ * Combines multiple filters with OR logic
212
+ */
213
+ export function or<T>(
214
+ ...filters: Array<(item: T) => boolean>
215
+ ): (item: T) => boolean {
216
+ return (item: T) => filters.some((f) => f(item));
217
+ }
218
+
219
+ /**
220
+ * Negates a filter
221
+ */
222
+ export function not<T>(filter: (item: T) => boolean): (item: T) => boolean {
223
+ return (item: T) => !filter(item);
224
+ }