@b2bc-devkit/sheetorm 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/dist/npm/SheetOrm.d.ts +41 -0
- package/dist/npm/SheetOrm.d.ts.map +1 -0
- package/dist/npm/SheetOrm.js +41 -0
- package/dist/npm/SheetOrm.js.map +1 -0
- package/dist/npm/core/Decorators.d.ts +145 -0
- package/dist/npm/core/Decorators.d.ts.map +1 -0
- package/dist/npm/core/Decorators.js +226 -0
- package/dist/npm/core/Decorators.js.map +1 -0
- package/dist/npm/core/Record.d.ts +167 -0
- package/dist/npm/core/Record.d.ts.map +1 -0
- package/dist/npm/core/Record.js +298 -0
- package/dist/npm/core/Record.js.map +1 -0
- package/dist/npm/core/RecordConstructor.d.ts +20 -0
- package/dist/npm/core/RecordConstructor.d.ts.map +1 -0
- package/dist/npm/core/RecordConstructor.js +2 -0
- package/dist/npm/core/RecordConstructor.js.map +1 -0
- package/dist/npm/core/RecordStatic.d.ts +19 -0
- package/dist/npm/core/RecordStatic.d.ts.map +1 -0
- package/dist/npm/core/RecordStatic.js +2 -0
- package/dist/npm/core/RecordStatic.js.map +1 -0
- package/dist/npm/core/Registry.d.ts +100 -0
- package/dist/npm/core/Registry.d.ts.map +1 -0
- package/dist/npm/core/Registry.js +206 -0
- package/dist/npm/core/Registry.js.map +1 -0
- package/dist/npm/core/SheetRepository.d.ts +331 -0
- package/dist/npm/core/SheetRepository.d.ts.map +1 -0
- package/dist/npm/core/SheetRepository.js +1041 -0
- package/dist/npm/core/SheetRepository.js.map +1 -0
- package/dist/npm/core/cache/MemoryCache.d.ts +67 -0
- package/dist/npm/core/cache/MemoryCache.d.ts.map +1 -0
- package/dist/npm/core/cache/MemoryCache.js +112 -0
- package/dist/npm/core/cache/MemoryCache.js.map +1 -0
- package/dist/npm/core/types/Entity.d.ts +21 -0
- package/dist/npm/core/types/Entity.d.ts.map +1 -0
- package/dist/npm/core/types/Entity.js +2 -0
- package/dist/npm/core/types/Entity.js.map +1 -0
- package/dist/npm/core/types/FieldDefinition.d.ts +21 -0
- package/dist/npm/core/types/FieldDefinition.d.ts.map +1 -0
- package/dist/npm/core/types/FieldDefinition.js +2 -0
- package/dist/npm/core/types/FieldDefinition.js.map +1 -0
- package/dist/npm/core/types/FieldType.d.ts +12 -0
- package/dist/npm/core/types/FieldType.d.ts.map +1 -0
- package/dist/npm/core/types/FieldType.js +2 -0
- package/dist/npm/core/types/FieldType.js.map +1 -0
- package/dist/npm/core/types/Filter.d.ts +16 -0
- package/dist/npm/core/types/Filter.d.ts.map +1 -0
- package/dist/npm/core/types/Filter.js +2 -0
- package/dist/npm/core/types/Filter.js.map +1 -0
- package/dist/npm/core/types/FilterOperator.d.ts +16 -0
- package/dist/npm/core/types/FilterOperator.d.ts.map +1 -0
- package/dist/npm/core/types/FilterOperator.js +2 -0
- package/dist/npm/core/types/FilterOperator.js.map +1 -0
- package/dist/npm/core/types/GroupResult.d.ts +14 -0
- package/dist/npm/core/types/GroupResult.d.ts.map +1 -0
- package/dist/npm/core/types/GroupResult.js +2 -0
- package/dist/npm/core/types/GroupResult.js.map +1 -0
- package/dist/npm/core/types/ICacheProvider.d.ts +20 -0
- package/dist/npm/core/types/ICacheProvider.d.ts.map +1 -0
- package/dist/npm/core/types/ICacheProvider.js +2 -0
- package/dist/npm/core/types/ICacheProvider.js.map +1 -0
- package/dist/npm/core/types/ISheetAdapter.d.ts +54 -0
- package/dist/npm/core/types/ISheetAdapter.d.ts.map +1 -0
- package/dist/npm/core/types/ISheetAdapter.js +2 -0
- package/dist/npm/core/types/ISheetAdapter.js.map +1 -0
- package/dist/npm/core/types/ISpreadsheetAdapter.d.ts +34 -0
- package/dist/npm/core/types/ISpreadsheetAdapter.d.ts.map +1 -0
- package/dist/npm/core/types/ISpreadsheetAdapter.js +2 -0
- package/dist/npm/core/types/ISpreadsheetAdapter.js.map +1 -0
- package/dist/npm/core/types/IndexDefinition.d.ts +16 -0
- package/dist/npm/core/types/IndexDefinition.d.ts.map +1 -0
- package/dist/npm/core/types/IndexDefinition.js +2 -0
- package/dist/npm/core/types/IndexDefinition.js.map +1 -0
- package/dist/npm/core/types/LifecycleHooks.d.ts +32 -0
- package/dist/npm/core/types/LifecycleHooks.d.ts.map +1 -0
- package/dist/npm/core/types/LifecycleHooks.js +2 -0
- package/dist/npm/core/types/LifecycleHooks.js.map +1 -0
- package/dist/npm/core/types/PaginatedResult.d.ts +18 -0
- package/dist/npm/core/types/PaginatedResult.d.ts.map +1 -0
- package/dist/npm/core/types/PaginatedResult.js +2 -0
- package/dist/npm/core/types/PaginatedResult.js.map +1 -0
- package/dist/npm/core/types/QueryOptions.d.ts +21 -0
- package/dist/npm/core/types/QueryOptions.d.ts.map +1 -0
- package/dist/npm/core/types/QueryOptions.js +2 -0
- package/dist/npm/core/types/QueryOptions.js.map +1 -0
- package/dist/npm/core/types/SortClause.d.ts +10 -0
- package/dist/npm/core/types/SortClause.d.ts.map +1 -0
- package/dist/npm/core/types/SortClause.js +2 -0
- package/dist/npm/core/types/SortClause.js.map +1 -0
- package/dist/npm/core/types/SystemColumns.d.ts +16 -0
- package/dist/npm/core/types/SystemColumns.d.ts.map +1 -0
- package/dist/npm/core/types/SystemColumns.js +16 -0
- package/dist/npm/core/types/SystemColumns.js.map +1 -0
- package/dist/npm/core/types/TableSchema.d.ts +24 -0
- package/dist/npm/core/types/TableSchema.d.ts.map +1 -0
- package/dist/npm/core/types/TableSchema.js +2 -0
- package/dist/npm/core/types/TableSchema.js.map +1 -0
- package/dist/npm/index/IndexMeta.d.ts +16 -0
- package/dist/npm/index/IndexMeta.d.ts.map +1 -0
- package/dist/npm/index/IndexMeta.js +2 -0
- package/dist/npm/index/IndexMeta.js.map +1 -0
- package/dist/npm/index/IndexStore.d.ts +399 -0
- package/dist/npm/index/IndexStore.d.ts.map +1 -0
- package/dist/npm/index/IndexStore.js +1104 -0
- package/dist/npm/index/IndexStore.js.map +1 -0
- package/dist/npm/index.d.ts +115 -0
- package/dist/npm/index.d.ts.map +1 -0
- package/dist/npm/index.js +367 -0
- package/dist/npm/index.js.map +1 -0
- package/dist/npm/query/Query.d.ts +176 -0
- package/dist/npm/query/Query.d.ts.map +1 -0
- package/dist/npm/query/Query.js +270 -0
- package/dist/npm/query/Query.js.map +1 -0
- package/dist/npm/query/QueryEngine.d.ts +140 -0
- package/dist/npm/query/QueryEngine.d.ts.map +1 -0
- package/dist/npm/query/QueryEngine.js +453 -0
- package/dist/npm/query/QueryEngine.js.map +1 -0
- package/dist/npm/storage/GoogleSheetAdapter.d.ts +120 -0
- package/dist/npm/storage/GoogleSheetAdapter.d.ts.map +1 -0
- package/dist/npm/storage/GoogleSheetAdapter.js +239 -0
- package/dist/npm/storage/GoogleSheetAdapter.js.map +1 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.d.ts +55 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.d.ts.map +1 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.js +99 -0
- package/dist/npm/storage/GoogleSpreadsheetAdapter.js.map +1 -0
- package/dist/npm/testing/ParityCatalog.d.ts +38 -0
- package/dist/npm/testing/ParityCatalog.d.ts.map +1 -0
- package/dist/npm/testing/ParityCatalog.js +400 -0
- package/dist/npm/testing/ParityCatalog.js.map +1 -0
- package/dist/npm/testing/RuntimeBenchmark.d.ts +37 -0
- package/dist/npm/testing/RuntimeBenchmark.d.ts.map +1 -0
- package/dist/npm/testing/RuntimeBenchmark.js +449 -0
- package/dist/npm/testing/RuntimeBenchmark.js.map +1 -0
- package/dist/npm/testing/RuntimeParity.d.ts +62 -0
- package/dist/npm/testing/RuntimeParity.d.ts.map +1 -0
- package/dist/npm/testing/RuntimeParity.js +4030 -0
- package/dist/npm/testing/RuntimeParity.js.map +1 -0
- package/dist/npm/utils/Serialization.d.ts +111 -0
- package/dist/npm/utils/Serialization.d.ts.map +1 -0
- package/dist/npm/utils/Serialization.js +281 -0
- package/dist/npm/utils/Serialization.js.map +1 -0
- package/dist/npm/utils/SheetOrmLogger.d.ts +31 -0
- package/dist/npm/utils/SheetOrmLogger.d.ts.map +1 -0
- package/dist/npm/utils/SheetOrmLogger.js +42 -0
- package/dist/npm/utils/SheetOrmLogger.js.map +1 -0
- package/dist/npm/utils/Uuid.d.ts +29 -0
- package/dist/npm/utils/Uuid.d.ts.map +1 -0
- package/dist/npm/utils/Uuid.js +86 -0
- package/dist/npm/utils/Uuid.js.map +1 -0
- package/license.md +18 -0
- package/package.json +77 -0
- package/readme.md +672 -0
|
@@ -0,0 +1,453 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* QueryEngine — pure in-memory filter, sort, paginate, and group engine.
|
|
3
|
+
*
|
|
4
|
+
* Every function operates on plain entity arrays and returns new arrays
|
|
5
|
+
* without mutating the input. The implementation is heavily optimised for
|
|
6
|
+
* the GAS V8 runtime (limited JIT, no SIMD, high GC cost):
|
|
7
|
+
*
|
|
8
|
+
* - Filters are **compiled** into predicate closures once; the hot loop
|
|
9
|
+
* invokes closures instead of evaluating operator strings.
|
|
10
|
+
* - Sort keys are **pre-extracted** into column-major arrays to avoid
|
|
11
|
+
* repeated field navigation during comparison callbacks.
|
|
12
|
+
* - The `in` operator converts to a `Set` when the value list exceeds
|
|
13
|
+
* 8 elements for O(1) membership checks.
|
|
14
|
+
* - String comparison operators (`contains`, `startsWith`, `search`)
|
|
15
|
+
* are case-insensitive and pre-lower the filter value once.
|
|
16
|
+
*
|
|
17
|
+
* @module QueryEngine
|
|
18
|
+
*/
|
|
19
|
+
import { SheetOrmLogger } from "../utils/SheetOrmLogger.js";
|
|
20
|
+
/**
|
|
21
|
+
* Split a field path into segments for nested property access.
|
|
22
|
+
*
|
|
23
|
+
* Single-segment paths (no dots or slashes) return `null` to signal the
|
|
24
|
+
* fast-path in {@link compileFieldAccessor} — a direct property read.
|
|
25
|
+
*
|
|
26
|
+
* @param field - Dot- or slash-separated field path (e.g. `"address.city"`).
|
|
27
|
+
* @returns Array of segments, or `null` for a simple field name.
|
|
28
|
+
*/
|
|
29
|
+
function splitFieldPath(field) {
|
|
30
|
+
if (field.indexOf(".") === -1 && field.indexOf("/") === -1)
|
|
31
|
+
return null;
|
|
32
|
+
return field.replace(/\//g, ".").split(".");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Pre-compile a field accessor closure for the given field path.
|
|
36
|
+
*
|
|
37
|
+
* Simple fields return a direct property reader; nested paths return a
|
|
38
|
+
* closure that walks each segment, returning `undefined` for missing
|
|
39
|
+
* intermediate objects.
|
|
40
|
+
*
|
|
41
|
+
* @param field - Dot-separated field path.
|
|
42
|
+
* @returns Accessor function: `(entity) => fieldValue`.
|
|
43
|
+
*/
|
|
44
|
+
function compileFieldAccessor(field) {
|
|
45
|
+
const parts = splitFieldPath(field);
|
|
46
|
+
if (parts === null) {
|
|
47
|
+
// Simple field — direct property access (fast path)
|
|
48
|
+
return (entity) => entity[field];
|
|
49
|
+
}
|
|
50
|
+
// Nested field — walk the path with null-safe navigation
|
|
51
|
+
return (entity) => {
|
|
52
|
+
let current = entity;
|
|
53
|
+
for (let i = 0; i < parts.length; i++) {
|
|
54
|
+
if (current == null)
|
|
55
|
+
return undefined;
|
|
56
|
+
current = current[parts[i]];
|
|
57
|
+
}
|
|
58
|
+
return current;
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Compile a single {@link Filter} into a fast predicate function.
|
|
63
|
+
*
|
|
64
|
+
* Pre-computes the field accessor and, for the `in` operator, converts
|
|
65
|
+
* large value arrays (>8 elements) to a `Set` for O(1) membership checks.
|
|
66
|
+
*
|
|
67
|
+
* Supported operators: `=`, `!=`, `<`, `>`, `<=`, `>=`, `contains`,
|
|
68
|
+
* `startsWith`, `in`, `search`.
|
|
69
|
+
*
|
|
70
|
+
* @param f - The filter to compile.
|
|
71
|
+
* @returns Predicate function: `(entity) => boolean`.
|
|
72
|
+
*/
|
|
73
|
+
function compileFilter(f) {
|
|
74
|
+
const accessor = compileFieldAccessor(f.field);
|
|
75
|
+
const op = f.operator;
|
|
76
|
+
const fv = f.value;
|
|
77
|
+
switch (op) {
|
|
78
|
+
case "=":
|
|
79
|
+
return (e) => accessor(e) === fv;
|
|
80
|
+
case "!=":
|
|
81
|
+
return (e) => accessor(e) !== fv;
|
|
82
|
+
case "<":
|
|
83
|
+
return (e) => {
|
|
84
|
+
const v = accessor(e);
|
|
85
|
+
if (typeof v !== typeof fv)
|
|
86
|
+
return false;
|
|
87
|
+
return v < fv;
|
|
88
|
+
};
|
|
89
|
+
case ">":
|
|
90
|
+
return (e) => {
|
|
91
|
+
const v = accessor(e);
|
|
92
|
+
if (typeof v !== typeof fv)
|
|
93
|
+
return false;
|
|
94
|
+
return v > fv;
|
|
95
|
+
};
|
|
96
|
+
case "<=":
|
|
97
|
+
return (e) => {
|
|
98
|
+
const v = accessor(e);
|
|
99
|
+
if (typeof v !== typeof fv)
|
|
100
|
+
return false;
|
|
101
|
+
return v <= fv;
|
|
102
|
+
};
|
|
103
|
+
case ">=":
|
|
104
|
+
return (e) => {
|
|
105
|
+
const v = accessor(e);
|
|
106
|
+
if (typeof v !== typeof fv)
|
|
107
|
+
return false;
|
|
108
|
+
return v >= fv;
|
|
109
|
+
};
|
|
110
|
+
case "contains": {
|
|
111
|
+
// Case-insensitive substring match
|
|
112
|
+
if (typeof fv !== "string")
|
|
113
|
+
return () => false;
|
|
114
|
+
const lowerC = fv.toLowerCase();
|
|
115
|
+
return (e) => {
|
|
116
|
+
const v = accessor(e);
|
|
117
|
+
return typeof v === "string" && v.toLowerCase().includes(lowerC);
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
case "startsWith": {
|
|
121
|
+
// Case-insensitive prefix match
|
|
122
|
+
if (typeof fv !== "string")
|
|
123
|
+
return () => false;
|
|
124
|
+
const lowerS = fv.toLowerCase();
|
|
125
|
+
return (e) => {
|
|
126
|
+
const v = accessor(e);
|
|
127
|
+
return typeof v === "string" && v.toLowerCase().startsWith(lowerS);
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
case "in": {
|
|
131
|
+
// Membership check — uses Set for large lists (>8) for O(1) lookups
|
|
132
|
+
if (!Array.isArray(fv))
|
|
133
|
+
return () => false;
|
|
134
|
+
if (fv.length > 8) {
|
|
135
|
+
const set = new Set(fv);
|
|
136
|
+
return (e) => set.has(accessor(e));
|
|
137
|
+
}
|
|
138
|
+
return (e) => fv.includes(accessor(e));
|
|
139
|
+
}
|
|
140
|
+
case "search": {
|
|
141
|
+
// Case-insensitive substring search (same as "contains" but semantically distinct —
|
|
142
|
+
// in SheetRepository.find() the "search" operator can leverage n-gram indexes)
|
|
143
|
+
if (typeof fv !== "string")
|
|
144
|
+
return () => false;
|
|
145
|
+
const lowerSr = fv.toLowerCase();
|
|
146
|
+
return (e) => {
|
|
147
|
+
const v = accessor(e);
|
|
148
|
+
return typeof v === "string" && v.toLowerCase().includes(lowerSr);
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
default:
|
|
152
|
+
return () => false;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Filter entities by an array of filters using AND logic.
|
|
157
|
+
*
|
|
158
|
+
* All predicates are compiled once; the hot loop uses a labeled `continue outer`
|
|
159
|
+
* to short-circuit on the first failing predicate per entity (minimal GC pressure).
|
|
160
|
+
*
|
|
161
|
+
* @typeParam T - Entity type.
|
|
162
|
+
* @param entities - Source array (not mutated).
|
|
163
|
+
* @param filters - Filters to apply (all must match — AND).
|
|
164
|
+
* @returns New array of matching entities.
|
|
165
|
+
*/
|
|
166
|
+
function filterEntities(entities, filters) {
|
|
167
|
+
if (!filters || filters.length === 0)
|
|
168
|
+
return entities;
|
|
169
|
+
// Compile all filter predicates once
|
|
170
|
+
const predicates = new Array(filters.length);
|
|
171
|
+
for (let i = 0; i < filters.length; i++) {
|
|
172
|
+
predicates[i] = compileFilter(filters[i]);
|
|
173
|
+
}
|
|
174
|
+
const len = entities.length;
|
|
175
|
+
const predLen = predicates.length;
|
|
176
|
+
const result = [];
|
|
177
|
+
// Labeled loop: `continue outer` skips to next entity on first failing predicate
|
|
178
|
+
outer: for (let i = 0; i < len; i++) {
|
|
179
|
+
const entity = entities[i];
|
|
180
|
+
for (let j = 0; j < predLen; j++) {
|
|
181
|
+
if (!predicates[j](entity))
|
|
182
|
+
continue outer;
|
|
183
|
+
}
|
|
184
|
+
result.push(entity);
|
|
185
|
+
}
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Filter entities with OR-connected groups of AND-connected filters.
|
|
190
|
+
*
|
|
191
|
+
* An entity matches if it satisfies **any** group (OR), where within each
|
|
192
|
+
* group all filters must match (AND). Empty groups never match.
|
|
193
|
+
*
|
|
194
|
+
* @typeParam T - Entity type.
|
|
195
|
+
* @param entities - Source array (not mutated).
|
|
196
|
+
* @param groups - Array of filter groups (outer=OR, inner=AND).
|
|
197
|
+
* @returns New array of matching entities.
|
|
198
|
+
*/
|
|
199
|
+
function filterEntitiesOr(entities, groups) {
|
|
200
|
+
if (!groups || groups.length === 0)
|
|
201
|
+
return entities;
|
|
202
|
+
// Compile each group into an array of predicate closures
|
|
203
|
+
const compiledGroups = new Array(groups.length);
|
|
204
|
+
for (let g = 0; g < groups.length; g++) {
|
|
205
|
+
const group = groups[g];
|
|
206
|
+
const predicates = new Array(group.length);
|
|
207
|
+
for (let i = 0; i < group.length; i++) {
|
|
208
|
+
predicates[i] = compileFilter(group[i]);
|
|
209
|
+
}
|
|
210
|
+
compiledGroups[g] = predicates;
|
|
211
|
+
}
|
|
212
|
+
const len = entities.length;
|
|
213
|
+
const numGroups = compiledGroups.length;
|
|
214
|
+
const result = [];
|
|
215
|
+
for (let i = 0; i < len; i++) {
|
|
216
|
+
const entity = entities[i];
|
|
217
|
+
let matched = false;
|
|
218
|
+
// Try each group — entity matches if ANY group's predicates all pass
|
|
219
|
+
for (let g = 0; g < numGroups; g++) {
|
|
220
|
+
const predicates = compiledGroups[g];
|
|
221
|
+
const predLen = predicates.length;
|
|
222
|
+
// An empty group never matches — it has no conditions to satisfy
|
|
223
|
+
let groupMatch = predLen > 0;
|
|
224
|
+
for (let j = 0; j < predLen; j++) {
|
|
225
|
+
if (!predicates[j](entity)) {
|
|
226
|
+
groupMatch = false;
|
|
227
|
+
break;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
if (groupMatch) {
|
|
231
|
+
matched = true;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (matched)
|
|
236
|
+
result.push(entity);
|
|
237
|
+
}
|
|
238
|
+
return result;
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Sort entities by multiple {@link SortClause} fields.
|
|
242
|
+
*
|
|
243
|
+
* **Performance strategy** (optimised for GAS V8):
|
|
244
|
+
*
|
|
245
|
+
* 1. Sort keys are **pre-extracted** into column-major arrays
|
|
246
|
+
* (`keys[sortIndex][entityIndex]`) so the comparator never calls
|
|
247
|
+
* field accessors — it reads from pre-populated primitive arrays.
|
|
248
|
+
* 2. An **index array** is sorted instead of the entities themselves,
|
|
249
|
+
* avoiding expensive object moves in the V8 sort implementation.
|
|
250
|
+
* 3. The final result is assembled from the sorted indices in one pass.
|
|
251
|
+
*
|
|
252
|
+
* Null handling: `null`/`undefined` values sort **first** (before any
|
|
253
|
+
* non-null value) regardless of direction.
|
|
254
|
+
*
|
|
255
|
+
* @typeParam T - Entity type.
|
|
256
|
+
* @param entities - Source array (not mutated).
|
|
257
|
+
* @param sorts - Sort clauses in priority order (first = primary sort).
|
|
258
|
+
* @returns New sorted array.
|
|
259
|
+
*/
|
|
260
|
+
function sortEntities(entities, sorts) {
|
|
261
|
+
if (!sorts || sorts.length === 0)
|
|
262
|
+
return entities;
|
|
263
|
+
const len = entities.length;
|
|
264
|
+
const numSorts = sorts.length;
|
|
265
|
+
// Pre-compile field accessors and direction multipliers (+1 for asc, -1 for desc)
|
|
266
|
+
const accessors = new Array(numSorts);
|
|
267
|
+
const directions = new Array(numSorts);
|
|
268
|
+
for (let s = 0; s < numSorts; s++) {
|
|
269
|
+
accessors[s] = compileFieldAccessor(sorts[s].field);
|
|
270
|
+
directions[s] = sorts[s].direction === "desc" ? -1 : 1;
|
|
271
|
+
}
|
|
272
|
+
// Pre-extract sort keys into column-major layout: keys[sortIndex][entityIndex]
|
|
273
|
+
// This avoids repeated field accessor calls during the sort comparator
|
|
274
|
+
const keys = new Array(numSorts);
|
|
275
|
+
for (let s = 0; s < numSorts; s++) {
|
|
276
|
+
const accessor = accessors[s];
|
|
277
|
+
const col = new Array(len);
|
|
278
|
+
for (let i = 0; i < len; i++) {
|
|
279
|
+
col[i] = accessor(entities[i]);
|
|
280
|
+
}
|
|
281
|
+
keys[s] = col;
|
|
282
|
+
}
|
|
283
|
+
// Build index array — sort indices instead of objects to avoid expensive moves
|
|
284
|
+
const indices = new Array(len);
|
|
285
|
+
for (let i = 0; i < len; i++)
|
|
286
|
+
indices[i] = i;
|
|
287
|
+
// Multi-key comparator using pre-extracted key columns
|
|
288
|
+
indices.sort((ai, bi) => {
|
|
289
|
+
for (let s = 0; s < numSorts; s++) {
|
|
290
|
+
const aVal = keys[s][ai];
|
|
291
|
+
const bVal = keys[s][bi];
|
|
292
|
+
let cmp;
|
|
293
|
+
if (aVal === bVal) {
|
|
294
|
+
cmp = 0;
|
|
295
|
+
}
|
|
296
|
+
else if (aVal == null) {
|
|
297
|
+
cmp = -1; // Nulls sort first
|
|
298
|
+
}
|
|
299
|
+
else if (bVal == null) {
|
|
300
|
+
cmp = 1; // Nulls sort first
|
|
301
|
+
}
|
|
302
|
+
else if (typeof aVal === "number" && typeof bVal === "number") {
|
|
303
|
+
cmp = aVal - bVal; // Numeric comparison (avoids string coercion)
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
cmp = String(aVal).localeCompare(String(bVal)); // String fallback
|
|
307
|
+
}
|
|
308
|
+
if (cmp !== 0)
|
|
309
|
+
return cmp * directions[s]; // Apply asc/desc multiplier
|
|
310
|
+
}
|
|
311
|
+
return 0; // All keys equal — stable order
|
|
312
|
+
});
|
|
313
|
+
// Build sorted result array from the sorted index permutation
|
|
314
|
+
const result = new Array(len);
|
|
315
|
+
for (let i = 0; i < len; i++) {
|
|
316
|
+
result[i] = entities[indices[i]];
|
|
317
|
+
}
|
|
318
|
+
return result;
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Apply pagination (offset + limit) to an entity array.
|
|
322
|
+
*
|
|
323
|
+
* Both `offset` and `limit` are sanitised: non-finite, negative, or
|
|
324
|
+
* fractional values are clamped to safe defaults (0 for offset, full
|
|
325
|
+
* length for limit).
|
|
326
|
+
*
|
|
327
|
+
* @typeParam T - Entity type.
|
|
328
|
+
* @param entities - Source array.
|
|
329
|
+
* @param offset - Number of items to skip from the start.
|
|
330
|
+
* @param limit - Maximum number of items to return.
|
|
331
|
+
* @returns Paginated result with metadata (total, hasNext, etc.).
|
|
332
|
+
*/
|
|
333
|
+
function paginateEntities(entities, offset, limit) {
|
|
334
|
+
const safeOffset = Number.isFinite(offset) && offset >= 0 ? Math.floor(offset) : 0;
|
|
335
|
+
const safeLimit = Number.isFinite(limit) && limit >= 0 ? Math.floor(limit) : entities.length;
|
|
336
|
+
const total = entities.length;
|
|
337
|
+
const sliced = entities.slice(safeOffset, safeOffset + safeLimit);
|
|
338
|
+
return {
|
|
339
|
+
items: sliced,
|
|
340
|
+
total,
|
|
341
|
+
offset: safeOffset,
|
|
342
|
+
limit: safeLimit,
|
|
343
|
+
hasNext: safeOffset + safeLimit < total,
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/**
|
|
347
|
+
* Group entities by a single field, producing one {@link GroupResult} per distinct value.
|
|
348
|
+
*
|
|
349
|
+
* Uses a `Map` keyed by the field value to collect entities in insertion
|
|
350
|
+
* order. The pre-compiled field accessor avoids repeated path resolution.
|
|
351
|
+
*
|
|
352
|
+
* @typeParam T - Entity type.
|
|
353
|
+
* @param entities - Source array (not mutated).
|
|
354
|
+
* @param field - Dot-separated field path to group by.
|
|
355
|
+
* @returns Array of group results, each containing key, count, and items.
|
|
356
|
+
*/
|
|
357
|
+
function groupEntities(entities, field) {
|
|
358
|
+
const accessor = compileFieldAccessor(field);
|
|
359
|
+
const groups = new Map();
|
|
360
|
+
// Distribute entities into groups by their field value
|
|
361
|
+
for (let i = 0, len = entities.length; i < len; i++) {
|
|
362
|
+
const entity = entities[i];
|
|
363
|
+
const key = accessor(entity);
|
|
364
|
+
const existing = groups.get(key);
|
|
365
|
+
if (existing) {
|
|
366
|
+
existing.push(entity);
|
|
367
|
+
}
|
|
368
|
+
else {
|
|
369
|
+
groups.set(key, [entity]);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Convert Map entries to GroupResult array
|
|
373
|
+
const results = [];
|
|
374
|
+
groups.forEach((items, key) => {
|
|
375
|
+
results.push({ key, count: items.length, items });
|
|
376
|
+
});
|
|
377
|
+
return results;
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Execute a complete query pipeline on an in-memory entity array.
|
|
381
|
+
*
|
|
382
|
+
* Pipeline stages (executed in order):
|
|
383
|
+
*
|
|
384
|
+
* 1. **Filter**: `whereGroups` (OR of ANDs) takes precedence over `where`
|
|
385
|
+
* (simple AND). If both are present, only `whereGroups` is applied.
|
|
386
|
+
* 2. **Sort**: Multi-field sort via {@link sortEntities}.
|
|
387
|
+
* 3. **Paginate**: `offset` and `limit` applied via `Array.slice`.
|
|
388
|
+
*
|
|
389
|
+
* This function does NOT call {@link paginateEntities} — it applies a
|
|
390
|
+
* lighter slice-only pagination without producing a `PaginatedResult`
|
|
391
|
+
* wrapper. The caller (typically {@link SheetRepository}) is responsible
|
|
392
|
+
* for wrapping the result if needed.
|
|
393
|
+
*
|
|
394
|
+
* @typeParam T - Entity type.
|
|
395
|
+
* @param entities - Full entity array to query against.
|
|
396
|
+
* @param options - Query options controlling filter, sort, and pagination.
|
|
397
|
+
* @returns Filtered, sorted, and paginated entity array.
|
|
398
|
+
*/
|
|
399
|
+
function executeQuery(entities, options) {
|
|
400
|
+
SheetOrmLogger.log(`[QueryEngine] executeQuery in=${entities.length}` +
|
|
401
|
+
` where=${options.where?.length ?? 0}` +
|
|
402
|
+
` whereGroups=${options.whereGroups?.length ?? 0}` +
|
|
403
|
+
` orderBy=${options.orderBy?.length ?? 0}` +
|
|
404
|
+
` offset=${options.offset ?? "-"} limit=${options.limit ?? "-"}`);
|
|
405
|
+
let result = entities;
|
|
406
|
+
// Stage 1: Filter — whereGroups (OR of ANDs) takes precedence over where (AND)
|
|
407
|
+
if (options.whereGroups && options.whereGroups.length > 0) {
|
|
408
|
+
result = filterEntitiesOr(result, options.whereGroups);
|
|
409
|
+
}
|
|
410
|
+
else if (options.where && options.where.length > 0) {
|
|
411
|
+
result = filterEntities(result, options.where);
|
|
412
|
+
}
|
|
413
|
+
// Stage 2: Sort — multi-field ordering
|
|
414
|
+
if (options.orderBy && options.orderBy.length > 0) {
|
|
415
|
+
result = sortEntities(result, options.orderBy);
|
|
416
|
+
}
|
|
417
|
+
// Stage 3: Paginate — slice-only (no PaginatedResult wrapper)
|
|
418
|
+
if (options.offset !== undefined || options.limit !== undefined) {
|
|
419
|
+
const rawOffset = options.offset ?? 0;
|
|
420
|
+
const offset = Number.isFinite(rawOffset) && rawOffset >= 0 ? Math.floor(rawOffset) : 0;
|
|
421
|
+
const limit = options.limit !== undefined
|
|
422
|
+
? Number.isFinite(options.limit) && options.limit >= 0
|
|
423
|
+
? Math.floor(options.limit)
|
|
424
|
+
: result.length
|
|
425
|
+
: result.length;
|
|
426
|
+
result = result.slice(offset, offset + limit);
|
|
427
|
+
}
|
|
428
|
+
SheetOrmLogger.log(`[QueryEngine] executeQuery out=${result.length}`);
|
|
429
|
+
return result;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Static facade exposing the query engine's pure functions as class methods.
|
|
433
|
+
*
|
|
434
|
+
* All methods are stateless — they operate on the provided entity array
|
|
435
|
+
* and return new arrays without side effects. The class form exists so
|
|
436
|
+
* that callers can reference `QueryEngine.filterEntities(...)` instead of
|
|
437
|
+
* importing individual module-level functions.
|
|
438
|
+
*/
|
|
439
|
+
export class QueryEngine {
|
|
440
|
+
}
|
|
441
|
+
/** Filter entities with AND-connected conditions. */
|
|
442
|
+
QueryEngine.filterEntities = filterEntities;
|
|
443
|
+
/** Filter entities with OR-connected groups of AND-connected conditions. */
|
|
444
|
+
QueryEngine.filterEntitiesOr = filterEntitiesOr;
|
|
445
|
+
/** Sort entities by multiple fields with asc/desc directions. */
|
|
446
|
+
QueryEngine.sortEntities = sortEntities;
|
|
447
|
+
/** Paginate entities and return metadata (total, hasNext, etc.). */
|
|
448
|
+
QueryEngine.paginateEntities = paginateEntities;
|
|
449
|
+
/** Group entities by a field into key/count/items buckets. */
|
|
450
|
+
QueryEngine.groupEntities = groupEntities;
|
|
451
|
+
/** Execute a full filter → sort → paginate pipeline. */
|
|
452
|
+
QueryEngine.executeQuery = executeQuery;
|
|
453
|
+
//# sourceMappingURL=QueryEngine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"QueryEngine.js","sourceRoot":"","sources":["../../../src/query/QueryEngine.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google Apps Script adapter implementing the ISheetAdapter interface.
|
|
3
|
+
*
|
|
4
|
+
* Wraps a single GAS `Sheet` object (one tab in a Spreadsheet) and
|
|
5
|
+
* translates 0-based data indexes used by the ORM into 1-based sheet
|
|
6
|
+
* rows required by the GAS Range API. Row 1 is always the header row;
|
|
7
|
+
* data rows start at row 2.
|
|
8
|
+
*
|
|
9
|
+
* All multi-cell reads/writes are performed with `getRange().getValues()`
|
|
10
|
+
* / `setValues()` to minimise GAS API calls (each call adds ~300 ms of
|
|
11
|
+
* latency in the Apps Script runtime).
|
|
12
|
+
*
|
|
13
|
+
* @module GoogleSheetAdapter
|
|
14
|
+
*/
|
|
15
|
+
import type { ISheetAdapter } from "../core/types/ISheetAdapter.js";
|
|
16
|
+
/**
|
|
17
|
+
* Production implementation of {@link ISheetAdapter} for Google Apps Script.
|
|
18
|
+
* Delegates every operation to the underlying `GoogleAppsScript.Spreadsheet.Sheet`.
|
|
19
|
+
*/
|
|
20
|
+
export declare class GoogleSheetAdapter implements ISheetAdapter {
|
|
21
|
+
/** The wrapped GAS Sheet object representing one spreadsheet tab. */
|
|
22
|
+
private sheet;
|
|
23
|
+
/**
|
|
24
|
+
* @param sheet - A GAS Sheet object obtained via Spreadsheet.getSheetByName()
|
|
25
|
+
* or Spreadsheet.insertSheet().
|
|
26
|
+
*/
|
|
27
|
+
constructor(sheet: GoogleAppsScript.Spreadsheet.Sheet);
|
|
28
|
+
/** Return the name of the underlying sheet tab. */
|
|
29
|
+
getName(): string;
|
|
30
|
+
/**
|
|
31
|
+
* Read the header row (row 1) and return the column names as strings.
|
|
32
|
+
* Returns an empty array if the sheet has no columns.
|
|
33
|
+
*/
|
|
34
|
+
getHeaders(): string[];
|
|
35
|
+
/**
|
|
36
|
+
* Overwrite the header row (row 1) with the given column names.
|
|
37
|
+
* No-op if the headers array is empty.
|
|
38
|
+
*/
|
|
39
|
+
setHeaders(headers: string[]): void;
|
|
40
|
+
/**
|
|
41
|
+
* Read all data rows (row 2+) as a 2D array.
|
|
42
|
+
* Returns an empty array if the sheet has no data rows.
|
|
43
|
+
*/
|
|
44
|
+
getAllData(): unknown[][];
|
|
45
|
+
/**
|
|
46
|
+
* Return the number of data rows (excludes the header row).
|
|
47
|
+
* Computed as `lastRow - 1`, where lastRow includes the header.
|
|
48
|
+
*/
|
|
49
|
+
getRowCount(): number;
|
|
50
|
+
/** Append a single row after the last occupied row using GAS appendRow(). */
|
|
51
|
+
appendRow(values: unknown[]): void;
|
|
52
|
+
/**
|
|
53
|
+
* Append multiple rows in a single setValues() API call.
|
|
54
|
+
* Much faster than calling appendRow() in a loop (~300 ms per call saved).
|
|
55
|
+
*/
|
|
56
|
+
appendRows(rows: unknown[][]): void;
|
|
57
|
+
/**
|
|
58
|
+
* Write rows starting at the given 0-based data index.
|
|
59
|
+
* Overwrites existing cells — used for batch-update operations.
|
|
60
|
+
*
|
|
61
|
+
* @param startRowIndex - 0-based data index (sheet row = index + 2).
|
|
62
|
+
* @param rows - 2D array of values.
|
|
63
|
+
*/
|
|
64
|
+
writeRowsAt(startRowIndex: number, rows: unknown[][]): void;
|
|
65
|
+
/**
|
|
66
|
+
* Overwrite a single data row.
|
|
67
|
+
*
|
|
68
|
+
* @param rowIndex - 0-based data index (sheet row = index + 2).
|
|
69
|
+
* @param values - Column values.
|
|
70
|
+
*/
|
|
71
|
+
updateRow(rowIndex: number, values: unknown[]): void;
|
|
72
|
+
/**
|
|
73
|
+
* Overwrite multiple rows, batching contiguous groups into single
|
|
74
|
+
* setValues() calls to minimise GAS API round-trips.
|
|
75
|
+
*
|
|
76
|
+
* Example: updating rows 3, 4, 5, 10, 11 produces two setValues() calls
|
|
77
|
+
* instead of five.
|
|
78
|
+
*/
|
|
79
|
+
updateRows(updates: Array<{
|
|
80
|
+
rowIndex: number;
|
|
81
|
+
values: unknown[];
|
|
82
|
+
}>): void;
|
|
83
|
+
/**
|
|
84
|
+
* Delete a single data row. GAS deleteRow() shifts all rows below up by one.
|
|
85
|
+
* @param rowIndex - 0-based data index.
|
|
86
|
+
*/
|
|
87
|
+
deleteRow(rowIndex: number): void;
|
|
88
|
+
/**
|
|
89
|
+
* Delete multiple data rows by index.
|
|
90
|
+
* Rows are deleted from bottom to top so that earlier indexes remain valid.
|
|
91
|
+
*/
|
|
92
|
+
deleteRows(rowIndexes: number[]): void;
|
|
93
|
+
/**
|
|
94
|
+
* Read a single data row.
|
|
95
|
+
* @param rowIndex - 0-based data index.
|
|
96
|
+
* @returns Array of cell values.
|
|
97
|
+
*/
|
|
98
|
+
getRow(rowIndex: number): unknown[];
|
|
99
|
+
/**
|
|
100
|
+
* Replace all data rows (row 2+) with the provided 2D array.
|
|
101
|
+
*
|
|
102
|
+
* If the new data has fewer rows than the old data, surplus old rows
|
|
103
|
+
* are cleared. If the new data is narrower (fewer columns), surplus
|
|
104
|
+
* columns in the written region are also cleared.
|
|
105
|
+
*/
|
|
106
|
+
replaceAllData(rows: unknown[][]): void;
|
|
107
|
+
/** Clear the entire sheet (headers + data). */
|
|
108
|
+
clear(): void;
|
|
109
|
+
/** Force-flush pending changes to the spreadsheet (calls SpreadsheetApp.flush()). */
|
|
110
|
+
flush(): void;
|
|
111
|
+
/**
|
|
112
|
+
* L1 optimisation: Write the header row and all data rows in a single
|
|
113
|
+
* setValues() API call. Row 1 = headers, row 2+ = data.
|
|
114
|
+
*
|
|
115
|
+
* Used for newly-created sheets to avoid a separate setHeaders() round-trip
|
|
116
|
+
* (~700 ms saved per new table at first save).
|
|
117
|
+
*/
|
|
118
|
+
writeAllRowsWithHeaders(headers: string[], rows: unknown[][]): void;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=GoogleSheetAdapter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GoogleSheetAdapter.d.ts","sourceRoot":"","sources":["../../../src/storage/GoogleSheetAdapter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gCAAgC,CAAC;AAGpE;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,aAAa;IACtD,qEAAqE;IACrE,OAAO,CAAC,KAAK,CAAqC;IAElD;;;OAGG;gBACS,KAAK,EAAE,gBAAgB,CAAC,WAAW,CAAC,KAAK;IAIrD,mDAAmD;IACnD,OAAO,IAAI,MAAM;IAIjB;;;OAGG;IACH,UAAU,IAAI,MAAM,EAAE;IAStB;;;OAGG;IACH,UAAU,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,IAAI;IAKnC;;;OAGG;IACH,UAAU,IAAI,OAAO,EAAE,EAAE;IAgBzB;;;OAGG;IACH,WAAW,IAAI,MAAM;IAOrB,6EAA6E;IAC7E,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;IAIlC;;;OAGG;IACH,UAAU,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI;IAWnC;;;;;;OAMG;IACH,WAAW,CAAC,aAAa,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI;IAW3D;;;;;OAKG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI;IAMpD;;;;;;OAMG;IACH,UAAU,CAAC,OAAO,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,EAAE,CAAA;KAAE,CAAC,GAAG,IAAI;IAiCzE;;;OAGG;IACH,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAKjC;;;OAGG;IACH,UAAU,CAAC,UAAU,EAAE,MAAM,EAAE,GAAG,IAAI;IAQtC;;;;OAIG;IACH,MAAM,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,EAAE;IAOnC;;;;;;OAMG;IACH,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI;IAuBvC,+CAA+C;IAC/C,KAAK,IAAI,IAAI;IAIb,qFAAqF;IACrF,KAAK,IAAI,IAAI;IAIb;;;;;;OAMG;IACH,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,EAAE,GAAG,IAAI;CASpE"}
|