corebasic 1.0.206 → 1.0.208

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,289 @@
1
+ // Lightweight Query Matcher (JavaScript)
2
+ // ========= Utility Functions =========
3
+
4
+ function toF64(value) {
5
+ if (value === null || value === undefined) return 0;
6
+ if (typeof value === 'number') return value;
7
+ if (typeof value === 'string') {
8
+ const parsed = parseFloat(value);
9
+ return isNaN(parsed) ? 0 : parsed;
10
+ }
11
+ if (typeof value === 'boolean') return value ? 1 : 0;
12
+ return 0;
13
+ }
14
+
15
+ function getValueAt(obj, key) {
16
+ if (obj === null || obj === undefined) return null;
17
+ if (typeof obj !== 'object') return null;
18
+ return obj[key] !== undefined ? obj[key] : null;
19
+ }
20
+
21
+ // ========= Comparison Primitives =========
22
+
23
+ function eqQuery(v1, v2) {
24
+ // Deep equality for objects/arrays, strict for primitives
25
+ if (v1 === v2) return true;
26
+ if (v1 === null || v2 === null) return v1 === v2;
27
+ if (typeof v1 !== typeof v2) {
28
+ // Try numeric comparison
29
+ if (typeof v1 === 'number' || typeof v2 === 'number') {
30
+ return toF64(v1) === toF64(v2);
31
+ }
32
+ return false;
33
+ }
34
+ if (typeof v1 === 'object') {
35
+ if (Array.isArray(v1) !== Array.isArray(v2)) return false;
36
+ if (Array.isArray(v1)) {
37
+ if (v1.length !== v2.length) return false;
38
+ for (let i = 0; i < v1.length; i++) {
39
+ if (!eqQuery(v1[i], v2[i])) return false;
40
+ }
41
+ return true;
42
+ }
43
+ const keys1 = Object.keys(v1);
44
+ const keys2 = Object.keys(v2);
45
+ if (keys1.length !== keys2.length) return false;
46
+ for (const key of keys1) {
47
+ if (!keys2.includes(key)) return false;
48
+ if (!eqQuery(v1[key], v2[key])) return false;
49
+ }
50
+ return true;
51
+ }
52
+ return false;
53
+ }
54
+
55
+ function gtQuery(v1, v2) {
56
+ return toF64(v1) > toF64(v2);
57
+ }
58
+
59
+ function gteQuery(v1, v2) {
60
+ return toF64(v1) >= toF64(v2);
61
+ }
62
+
63
+ function ltQuery(v1, v2) {
64
+ return toF64(v1) < toF64(v2);
65
+ }
66
+
67
+ function lteQuery(v1, v2) {
68
+ return toF64(v1) <= toF64(v2);
69
+ }
70
+
71
+ function existsQuery(v1, v2) {
72
+ const exists = v1 !== null && v1 !== undefined;
73
+ const condition = typeof v2 === 'boolean' ? v2 : true;
74
+ return condition === exists;
75
+ }
76
+
77
+ function beginQuery(input, arg) {
78
+ if (typeof input === 'string' && typeof arg === 'string') {
79
+ return input.startsWith(arg);
80
+ }
81
+ return false;
82
+ }
83
+
84
+ function endQuery(input, arg) {
85
+ if (typeof input === 'string' && typeof arg === 'string') {
86
+ return input.endsWith(arg);
87
+ }
88
+ return false;
89
+ }
90
+
91
+ function containsQuery(input, arg, caseSensitive = true) {
92
+ if (typeof input !== 'string') return false;
93
+
94
+ if (Array.isArray(arg)) {
95
+ if (arg.length === 0) return false;
96
+ let allMatch = true;
97
+ for (const v of arg) {
98
+ let needle = typeof v === 'string' ? v : String(v);
99
+ let haystack = input;
100
+ if (!caseSensitive) {
101
+ needle = needle.toLowerCase();
102
+ haystack = haystack.toLowerCase();
103
+ }
104
+ if (!haystack.includes(needle)) {
105
+ allMatch = false;
106
+ break;
107
+ }
108
+ }
109
+ return allMatch;
110
+ }
111
+
112
+ if (typeof arg === 'string') {
113
+ let needle = arg;
114
+ let haystack = input;
115
+ if (!caseSensitive) {
116
+ needle = needle.toLowerCase();
117
+ haystack = haystack.toLowerCase();
118
+ }
119
+ return haystack.includes(needle);
120
+ }
121
+
122
+ return false;
123
+ }
124
+
125
+ // ========= Query Commands =========
126
+
127
+ function inQuery(input, args) {
128
+ if (Array.isArray(args)) {
129
+ for (const item of args) {
130
+ if (eqQuery(input, item)) return true;
131
+ }
132
+ return false;
133
+ }
134
+ if (args !== null && typeof args === 'object') return false;
135
+ return eqQuery(input, args);
136
+ }
137
+
138
+ function elemMatchQuery(input, query) {
139
+ if (!Array.isArray(input)) return false;
140
+
141
+ const term = query.$ || '$';
142
+ const captureLimit = query.$captureLimit || 0;
143
+ let countMatched = 0;
144
+
145
+ for (let index = 0; index < input.length; index++) {
146
+ if (andQuery(input[index], query)) {
147
+ if (captureLimit > 0 && countMatched >= captureLimit) break;
148
+ countMatched++;
149
+ }
150
+ }
151
+
152
+ return countMatched > 0;
153
+ }
154
+
155
+ function lengthQuery(input, arg) {
156
+ let len;
157
+ if (Array.isArray(input)) {
158
+ len = input.length;
159
+ } else if (typeof input === 'string') {
160
+ len = input.length;
161
+ } else {
162
+ return false;
163
+ }
164
+
165
+ if (arg !== null && typeof arg === 'object' && !Array.isArray(arg)) {
166
+ return andQuery(len, arg);
167
+ }
168
+ if (typeof arg === 'number') {
169
+ return len === arg;
170
+ }
171
+ return false;
172
+ }
173
+
174
+ function icaseQuery(input, arg) {
175
+ if (typeof input !== 'string') return false;
176
+
177
+ const lowerInput = input.toLowerCase();
178
+
179
+ if (arg !== null && typeof arg === 'object' && !Array.isArray(arg)) {
180
+ return andQuery(lowerInput, arg);
181
+ }
182
+ if (typeof arg === 'string') {
183
+ return eqQuery(lowerInput, arg.toLowerCase());
184
+ }
185
+ return false;
186
+ }
187
+
188
+ function selectCommand(key, input, value) {
189
+ switch (key) {
190
+ // Comparison
191
+ case '$':
192
+ return true;
193
+ case '$eq':
194
+ return eqQuery(input, value);
195
+ case '$ne':
196
+ return !eqQuery(input, value);
197
+ case '$gt':
198
+ return gtQuery(input, value);
199
+ case '$gte':
200
+ return gteQuery(input, value);
201
+ case '$lt':
202
+ return ltQuery(input, value);
203
+ case '$lte':
204
+ return lteQuery(input, value);
205
+ case '$in':
206
+ return inQuery(input, value);
207
+ case '$nin':
208
+ return !inQuery(input, value);
209
+
210
+ // Logical
211
+ case '$not':
212
+ return !andQuery(input, value);
213
+ case '$exists':
214
+ return existsQuery(input, value);
215
+ case '$or':
216
+ return orQuery(input, value);
217
+ case '$nor':
218
+ return !orQuery(input, value);
219
+ case '$and':
220
+ if (!Array.isArray(value)) return false;
221
+ for (const item of value) {
222
+ if (!andQuery(input, item)) return false;
223
+ }
224
+ return true;
225
+
226
+ // Array
227
+ case '$elemMatch':
228
+ return elemMatchQuery(input, value);
229
+ case '$length':
230
+ return lengthQuery(input, value);
231
+
232
+ // String matching
233
+ case '$icase':
234
+ return icaseQuery(input, value);
235
+ case '$begin':
236
+ return beginQuery(input, value);
237
+ case '$end':
238
+ return endQuery(input, value);
239
+ case '$contains':
240
+ return containsQuery(input, value);
241
+
242
+ default:
243
+ if (key.startsWith('$')) return true; // Unknown $keys pass through
244
+ return false;
245
+ }
246
+ }
247
+
248
+ function orQuery(input, value) {
249
+ if (!Array.isArray(value)) return false;
250
+ for (const item of value) {
251
+ if (andQuery(input, item)) return true;
252
+ }
253
+ return false;
254
+ }
255
+
256
+ function andQuery(input, qvalue) {
257
+ if (qvalue === null || qvalue === undefined) {
258
+ return eqQuery(input, qvalue);
259
+ }
260
+
261
+ if (typeof qvalue === 'object' && !Array.isArray(qvalue)) {
262
+ for (const [key, value] of Object.entries(qvalue)) {
263
+ if (key.startsWith('$')) {
264
+ if (!selectCommand(key, input, value)) {
265
+ return false;
266
+ }
267
+ } else {
268
+ const v = (typeof value === 'object' && value !== null && !Array.isArray(value))
269
+ ? andQuery(getValueAt(input, key), value)
270
+ : eqQuery(getValueAt(input, key), value);
271
+ if (!v) return false;
272
+ }
273
+ }
274
+ return true;
275
+ }
276
+
277
+ if (Array.isArray(qvalue)) {
278
+ return false;
279
+ }
280
+
281
+ return eqQuery(input, qvalue);
282
+ }
283
+
284
+ // ========= Main Entry Point =========
285
+
286
+ export function search(data, query) {
287
+ return andQuery(data, query);
288
+ }
289
+
@@ -0,0 +1,109 @@
1
+ import {entries, reduceRanges, extractBounds, getNormalizedBounds} from './index.js'
2
+ import {formatDate, fillDates} from './date.js'
3
+
4
+
5
+
6
+ const EXPLICIT_SUFFIX_POLICY_TYPES = new Set([ "string", "date", "number" ]);
7
+ export async function suffix(doc, policies, insertMode, arg) {
8
+ let suffixes = [""]
9
+ for (const policy of policies) {
10
+
11
+ const value = typeof policy.value === "string" ? policy.value.trim() : undefined
12
+ if ("value" in policy && !value)
13
+ throw new Error(`Error: Invalid suffix policy fixed 'value' specified for key in Dip`)
14
+
15
+ let keys = value ? [value] : entries(policy.key, doc);
16
+ const [policy_type, format] = (policy.type ?? "string").split(':')
17
+
18
+ if (!EXPLICIT_SUFFIX_POLICY_TYPES.has(policy_type))
19
+ throw new Error(`Error: Invalid suffix policy type specified for key ${policy.key} in Dip`)
20
+
21
+ if (policy_type === "string" && keys.filter(item => typeof item === "object").length)
22
+ throw new Error(`Error: Invalid numeric bounds on string type in suffix policy specified for key ${policy.key} in Dip`)
23
+
24
+ if (policy_type === "date") {
25
+ if (!format?.trim())
26
+ throw new Error(`Error: Invalid date format in suffix policy type specified for key ${policy.key} in Dip`)
27
+ if (format.includes('/'))
28
+ throw new Error(`Error: Invalid date format containing '/' in suffix policy type specified for key ${policy.key} in Dip`)
29
+ }
30
+
31
+ const ls_threshold = policy.ls_threshold ?? 5000
32
+
33
+ // ====== Range ======
34
+ let ranges = keys.filter(item => typeof item === "object")
35
+ keys = keys.filter(item => typeof item !== "object")
36
+
37
+ if (policy_type === "date") {
38
+ keys = keys.map(key => formatDate(key, format))
39
+ if (ranges.length) {
40
+ ranges = extractBounds(reduceRanges(ranges))
41
+ const from = ranges.from ?? policy.min // policy.min is always inclusive i.e $gte
42
+ const to = ranges.to ?? policy.max // policy.max is always inclusive i.e $lte
43
+ const fromOp = ranges.from ? ranges.fromOp : "$gte" // policy.min is $gte
44
+ const toOp = ranges.to ? ranges.toOp : "$lte" // policy.max is $lte
45
+ if (from === undefined || to === undefined) {
46
+ keys = [] // Fall back to Dip.operation("ls").
47
+ console.warn(`Warn: Missing suffix policy min/max bound for ${policy_type} range specified for key ${policy.key} in Dip`)
48
+ } else {
49
+ ranges = fillDates({from, to, fromOp, toOp}, format)
50
+ // console.log(ranges)
51
+ if (ranges.length > ls_threshold) {
52
+ keys = [] // Too many suffixes to enumerate. Fall back to Dip.operation("ls").
53
+ console.warn(`Warn: Suffix ls_threshold exceeded for key ${policy.key} in Dip`)
54
+ } else
55
+ keys.push(...ranges)
56
+ }
57
+ }
58
+ keys = [...new Set(keys)];
59
+ } else if (policy_type === "number" && ranges.length) {
60
+ ranges = getNormalizedBounds(extractBounds(reduceRanges(ranges)), "number")
61
+ const from = ranges.from ?? policy.min // policy.min is always inclusive i.e $gte
62
+ const to = ranges.to ?? policy.max // policy.max is always inclusive i.e $lte
63
+ if (from === undefined || to === undefined) {
64
+ keys = [] // Fall back to Dip.operation("ls").
65
+ console.warn(`Warn: Missing suffix policy min/max bound for ${policy_type} range specified for key ${policy.key} in Dip`)
66
+ } else {
67
+ if (to - from + 1 > ls_threshold) {
68
+ keys = [] // Too many suffixes to enumerate. Fall back to Dip.operation("ls").
69
+ console.warn(`Warn: Suffix ls_threshold exceeded for key ${policy.key} in Dip`)
70
+ } else {
71
+ for (let i = from; i <= to; i++)
72
+ keys.push(i)
73
+ }
74
+ keys = [...new Set(keys)];
75
+ }
76
+ }
77
+ // =================
78
+
79
+ let ls = false
80
+ let suffixAssociatedKeys = {}
81
+
82
+ if (!keys.length && insertMode)
83
+ throw new Error(`Error: Missing suffix policy key ${policy.key} in Dip`)
84
+ if (!keys.length && !insertMode) {
85
+ ls = true
86
+ for (const suffix of suffixes) {
87
+ const dirs = (await Dip.operation("ls", { db: arg.db, collection: arg.collection, suffix })).filter(dir => !dir.startsWith('chunk-'))
88
+ suffixAssociatedKeys[suffix] = dirs
89
+ }
90
+ console.warn(`Warn: Query omitted suffix policy key ${policy.key} in Dip. Falling back to Dip.operation(ls), incurring additional performance and network round-trip overhead.`)
91
+ }
92
+ if (!ls && keys.some(key => typeof key !== "number" && typeof key !== "string")) // Dates are already string by this point
93
+ throw new Error(`Error: Invalid value for suffix policy key ${policy.key} in Dip`)
94
+
95
+ // =================
96
+
97
+ const next = [];
98
+ for (const suffix of suffixes) {
99
+ const currentKeys = ls ? suffixAssociatedKeys[suffix] : keys;
100
+ for (const key of currentKeys) {
101
+ next.push(`${suffix}/${key}`);
102
+ }
103
+ }
104
+ suffixes = next; // The "dead branches" disappear automatically because they never contribute any children.
105
+
106
+
107
+ }
108
+ return suffixes.length === 1 && suffixes[0] === "" ? [] : suffixes
109
+ }