corebasic 1.0.205 → 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,103 @@
1
+ import {getNormalizedBounds} from './index.js'
2
+
3
+
4
+ const MONTH_SHORT = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]
5
+ const MONTH_LONG = ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"]
6
+
7
+ export function formatDate(value, format) {
8
+ const date = new Date(value)
9
+
10
+ if (Number.isNaN(date.getTime()))
11
+ throw new Error(`Error: Invalid date: ${value} in suffix policy specified for key in Dip`)
12
+
13
+
14
+ const day = date.getDate();
15
+ const monthIndex = date.getMonth();
16
+ const year = date.getFullYear();
17
+
18
+ // Pad single digits with a leading zero
19
+ const dd = String(day).padStart(2, '0');
20
+ const mm = String(monthIndex + 1).padStart(2, '0');
21
+ const yy = String(year).slice(-2);
22
+
23
+
24
+ // return format
25
+ // .replace('YYYY', year)
26
+ // .replace('YY', yy)
27
+ // .replace('MMMM', MONTH_LONG[monthIndex])
28
+ // .replace('MMM', MONTH_SHORT[monthIndex])
29
+ // .replace('MM', mm)
30
+ // .replace('DD', dd)
31
+ // .replace('D', day);
32
+
33
+
34
+ const replacements = {
35
+ YYYY: String(year),
36
+ YY: String(year).slice(-2),
37
+ MMMM: MONTH_LONG[monthIndex],
38
+ MMM: MONTH_SHORT[monthIndex],
39
+ MM: String(monthIndex + 1).padStart(2, "0"),
40
+ DD: String(day).padStart(2, "0"),
41
+ D: String(day),
42
+ };
43
+
44
+ return format.replace(/YYYY|MMMM|MMM|YY|MM|DD|D/g, token => replacements[token]);
45
+ }
46
+
47
+
48
+ // 📋 Supported Formats
49
+ // YYYY: 4-digit year (e.g., 2026)
50
+ // YY: 2-digit year (e.g., 26)
51
+ // MMMM: Full month name (e.g., July)
52
+ // MMM: Short month name (e.g., Jul)
53
+ // MM: 2-digit padded month (e.g., 07)
54
+ // DD: 2-digit padded day (e.g., 09)
55
+ // D: Unpadded day (e.g., 9)
56
+
57
+
58
+
59
+ // // let a = new Date()
60
+ // // let a = "2026-11-1"
61
+ // let a = new Date().getTime()
62
+ // let b = formatDate(a, "YYYY/DD-MMMM")
63
+ // console.log(b)
64
+ //
65
+
66
+
67
+
68
+ export function fillDates(bounds_t, format) {
69
+ const result = [];
70
+
71
+ let step;
72
+ if (format.includes("D"))
73
+ step = "day";
74
+ else if (format.includes("M"))
75
+ step = "month";
76
+ else
77
+ step = "year";
78
+
79
+ let bounds = { ...bounds_t, from: new Date(bounds_t.from), to: new Date(bounds_t.to) }
80
+
81
+ bounds = getNormalizedBounds(bounds, "date", step)
82
+
83
+ while (bounds.from <= bounds.to) {
84
+ result.push(formatDate(bounds.from, format));
85
+
86
+ switch (step) {
87
+ case "day":
88
+ bounds.from.setDate(bounds.from.getDate() + 1);
89
+ break;
90
+ case "month":
91
+ bounds.from.setMonth(bounds.from.getMonth() + 1);
92
+ break;
93
+ case "year":
94
+ bounds.from.setFullYear(bounds.from.getFullYear() + 1);
95
+ break;
96
+ }
97
+ }
98
+
99
+ return [...new Set(result)];
100
+ }
101
+
102
+
103
+
@@ -0,0 +1,249 @@
1
+
2
+
3
+
4
+ function flatten_id_values(target_key, values) {
5
+ let flattened = [];
6
+ flatten_value(target_key, values, flattened, false);
7
+ return flattened
8
+ }
9
+
10
+ function flatten_value(target_key, value, flattened, ancestor_has_id) {
11
+ const is_array = Array.isArray(value)
12
+ const is_object = value !== null && !is_array && typeof value === "object";
13
+ const is_regular = typeof value === "boolean" || typeof value === "number" || typeof value === "string"
14
+ const map = value
15
+ const arr = value
16
+
17
+ if (is_regular) {
18
+ flattened.push(value);
19
+ } else if (is_array) {
20
+ for (let v of arr) {
21
+ flatten_value(target_key, v, flattened, ancestor_has_id);
22
+ }
23
+ } else if (is_object) {
24
+
25
+ // {$nin}, {$not: {$in: []}} & $nor means Exclude. Indexes ensures everything else is excluded. $nin & $in with same id can be excluded but smells bad query thus punishable and so must incur the cost of fetching.
26
+
27
+ // Ensure _id is processed before entering the loop, otherwise a query { _id: 1, $or: [{_id: 2}, {}] } will skip _id: 1
28
+ if (!ancestor_has_id && Object.hasOwn(map, target_key)) {
29
+ flatten_value(target_key, map[target_key], flattened, true); // ancestor_has_id: true safely skips {_id: {_id: 1}} situation
30
+ }
31
+
32
+
33
+ for (let key in map) {
34
+ let val = map[key]
35
+
36
+ if (key === "$eq") {
37
+ flatten_value(target_key, val, flattened, ancestor_has_id);
38
+ } else if (key == "$gt" || key === "$gte" || key === "$lt" || key === "$lte") {
39
+ flattened.push({[key]: val});
40
+ } else if (key == "$in") {
41
+ // Only select $in, $eq, or (direct value which is done above in the match value section)
42
+ let arr = val
43
+ if (Array.isArray(arr)) {
44
+ for (let v of arr) {
45
+ flatten_value(target_key, v, flattened, ancestor_has_id);
46
+ }
47
+ }
48
+ } else if (key === "$and" && Array.isArray(val)) {
49
+ flatten_value(target_key, val, flattened, ancestor_has_id); // recursively process all values
50
+ } else if (key === "$or" && Array.isArray(val)) {
51
+ // Store prior count.
52
+ let checkpoint = flattened.length;
53
+ // Every branch must produce 1 or more candidate _id.
54
+ // Recursively, inner $or rollback/capture correctly affects outer $or rollback/capture. So works correctly.
55
+ let arr = val
56
+ if (Array.isArray(arr)) {
57
+ for (let branch of arr) {
58
+ let before = flattened.length;
59
+
60
+ flatten_value(target_key, branch, flattened, ancestor_has_id);
61
+
62
+ if (flattened.length === before) {
63
+ // branch produced no candidate _ids. Rollback to checkpoint.
64
+ flattened.length = checkpoint;
65
+ return;
66
+ }
67
+ }
68
+ }
69
+ }
70
+ }
71
+ }
72
+ }
73
+
74
+
75
+ export function entries(target_key, query, binary_slice) {
76
+ let result = flatten_id_values(target_key, query); // extract all valid _id
77
+
78
+ // let cows = result.map(v => extract_direct_id(v, binary_slice))
79
+
80
+ // // cows.sort_by(|a, b| a.as_ref().cmp(b.as_ref())); // sort for improved forward processing because kv engine keys are already sorted.
81
+ // cows.sort_unstable_by(|a, b| a.as_ref().cmp(b.as_ref())); // sort for improved forward processing because kv engine keys are already sorted.
82
+ //
83
+ // // unique_bytes(cows)
84
+ // unique_sorted_bytes(cows) // Leverage already sorted so only need comparisons with the previous item.
85
+
86
+ const objects = result.filter(item => typeof item === "object")
87
+ const values = result.filter(item => typeof item !== "object")
88
+
89
+ let unique = [...new Set(values)];
90
+ return unique.concat(objects)
91
+ }
92
+
93
+
94
+ // NOTE:
95
+ // reduceRanges() intentionally computes a conservative (wider) approximation.
96
+ // It is used only for suffix policy planning, where false positives are acceptable
97
+ // (extra suffix scans) but false negatives are not.
98
+ export function reduceRanges(values) {
99
+ let gt = null;
100
+ let gte = null;
101
+ let lt = null;
102
+ let lte = null;
103
+ const other = [];
104
+
105
+ for (const value of values) {
106
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
107
+ other.push(value);
108
+ continue;
109
+ }
110
+
111
+ if ("$gt" in value) {
112
+ if (gt === null || value.$gt < gt)
113
+ gt = value.$gt;
114
+ } else if ("$gte" in value) {
115
+ if (gte === null || value.$gte < gte)
116
+ gte = value.$gte;
117
+ } else if ("$lt" in value) {
118
+ if (lt === null || value.$lt > lt)
119
+ lt = value.$lt;
120
+ } else if ("$lte" in value) {
121
+ if (lte === null || value.$lte > lte)
122
+ lte = value.$lte;
123
+ } else {
124
+ other.push(value);
125
+ }
126
+ }
127
+
128
+ // Resolve lower bound.
129
+ if (gt !== null || gte !== null) {
130
+ if (gt === null) {
131
+ other.push({ $gte: gte });
132
+ } else if (gte === null) {
133
+ other.push({ $gt: gt });
134
+ } else if (gt < gte) {
135
+ other.push({ $gt: gt });
136
+ } else if (gte < gt) {
137
+ other.push({ $gte: gte });
138
+ } else {
139
+ // Same value: inclusive is wider.
140
+ other.push({ $gte: gt });
141
+ }
142
+ }
143
+
144
+ // Resolve upper bound.
145
+ if (lt !== null || lte !== null) {
146
+ if (lt === null) {
147
+ other.push({ $lte: lte });
148
+ } else if (lte === null) {
149
+ other.push({ $lt: lt });
150
+ } else if (lt > lte) {
151
+ other.push({ $lt: lt });
152
+ } else if (lte > lt) {
153
+ other.push({ $lte: lte });
154
+ } else {
155
+ // Same value: inclusive is wider.
156
+ other.push({ $lte: lt });
157
+ }
158
+ }
159
+
160
+ return other;
161
+ }
162
+
163
+ // reduceRanges() resolves conflicting bounds before extractBounds() runs.
164
+ // extractBounds() receives at most one lower bound and atmost one upper bound. reduceRanges() ensures this
165
+ // The for loop in extractBounds() may look slightly misleading because it looks like it is designed to resolve conflicts: but reduceRanges() has already done the conflict resolution.
166
+ // NOTE CRITICAL: extractBounds() MUST ONLY BE RUN ON THE OUTPUT OF reduceRanges()
167
+ export function extractBounds(values) {
168
+ let lower = null;
169
+ let upper = null;
170
+ let lowerOp = null
171
+ let upperOp = null
172
+ const remaining = [];
173
+
174
+ for (const value of values) {
175
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
176
+ remaining.push(value);
177
+ continue;
178
+ }
179
+
180
+ if ("$gt" in value) {
181
+ lower = value.$gt;
182
+ lowerOp = "$gt"
183
+ } else if ("$gte" in value) {
184
+ lower = value.$gte;
185
+ lowerOp = "$gte"
186
+ } else if ("$lt" in value) {
187
+ upper = value.$lt;
188
+ upperOp = "$lt"
189
+ } else if ("$lte" in value) {
190
+ upper = value.$lte;
191
+ upperOp = "$lte"
192
+ } else {
193
+ remaining.push(value);
194
+ }
195
+ }
196
+
197
+ return {
198
+ values: remaining,
199
+ from: lower,
200
+ to: upper,
201
+ fromOp: lowerOp,
202
+ toOp: upperOp,
203
+ };
204
+ }
205
+
206
+ // Converts exclusive bounds ($gt/$lt) into inclusive bounds. Mutates if date type
207
+ export function getNormalizedBounds(bounds_t, type, step) {
208
+ let bounds = {...bounds_t}
209
+
210
+ if (type === "number") {
211
+ if (bounds.fromOp === "$gt")
212
+ bounds.from++;
213
+ if (bounds.toOp === "$lt")
214
+ bounds.to--;
215
+ } else if (type === "date") {
216
+ if (step === "day") {
217
+ if (bounds.fromOp === "$gt")
218
+ bounds.from.setDate(bounds.from.getDate() + 1);
219
+ if (bounds.toOp === "$lt")
220
+ bounds.to.setDate(bounds.to.getDate() - 1);
221
+ } else if (step === "month") {
222
+ if (bounds.fromOp === "$gt")
223
+ bounds.from.setMonth(bounds.from.getMonth() + 1);
224
+ if (bounds.toOp === "$lt")
225
+ bounds.to.setMonth(bounds.to.getMonth() - 1);
226
+ } else {
227
+ if (bounds.fromOp === "$gt")
228
+ bounds.from.setFullYear(bounds.from.getFullYear() + 1);
229
+ if (bounds.toOp === "$lt")
230
+ bounds.to.setFullYear(bounds.to.getFullYear() - 1);
231
+ }
232
+ }
233
+
234
+ return bounds
235
+ }
236
+
237
+ // console.log('=============')
238
+ // let a = entries("date", {
239
+ // $or: [
240
+ // {date: {$gt: 10, $lt: 20}},
241
+ // {date: {$gt: 3, $lt: 90}},
242
+ // {date: {$gt: 15, $lt: 25}},
243
+ // {date: 5},
244
+ // // {},
245
+ // {date: 5},
246
+ // ]
247
+ // })
248
+ // a = reduce_ranges(a)
249
+ // console.log(a)
@@ -0,0 +1,156 @@
1
+ import {entries, reduceRanges, extractBounds} from './index.js'
2
+ import {suffix} from './suffix.js'
3
+
4
+
5
+
6
+
7
+ export function overlaps(a, b) {
8
+ const aFrom = a.from ?? -Infinity;
9
+ const aTo = a.to ?? Infinity;
10
+
11
+ const bFrom = b.from ?? -Infinity;
12
+ const bTo = b.to ?? Infinity;
13
+
14
+
15
+
16
+ const left = Math.max(aFrom, bFrom);
17
+ const right = Math.min(aTo, bTo);
18
+
19
+ if (left < right)
20
+ return true;
21
+
22
+ if (left > right)
23
+ return false;
24
+
25
+ // left === right, touching at one point.
26
+ const leftClosed =
27
+ (left === aFrom ? a.fromOp === "$gte" : a.toOp === "$lte") &&
28
+ (left === bFrom ? b.fromOp === "$gte" : b.toOp === "$lte");
29
+
30
+ return leftClosed;
31
+
32
+ // return Math.max(aFrom, bFrom) <= Math.min(aTo, bTo);
33
+ }
34
+
35
+
36
+
37
+
38
+ export function contains(bounds, value) {
39
+ const from = bounds.from ?? -Infinity;
40
+ const to = bounds.to ?? Infinity;
41
+
42
+ if (value < from)
43
+ return false;
44
+
45
+ if (value > to)
46
+ return false;
47
+
48
+ if (value === from && bounds.fromOp === "$gt")
49
+ return false;
50
+
51
+ if (value === to && bounds.toOp === "$lt")
52
+ return false;
53
+
54
+ return true;
55
+
56
+ // return value >= from && value <= to;
57
+ }
58
+
59
+ export function intersects(w_values, w_bounds, q_values, q_bounds) {
60
+ // value ↔ value
61
+ for (const value of w_values) {
62
+ if (q_values.includes(value))
63
+ return true;
64
+ }
65
+
66
+ // value(when) ↔ range(query)
67
+ if (q_bounds) {
68
+ for (const value of w_values) {
69
+ if (contains(q_bounds, value))
70
+ return true;
71
+ }
72
+ }
73
+
74
+ // value(query) ↔ range(when)
75
+ if (w_bounds) {
76
+ for (const value of q_values) {
77
+ if (contains(w_bounds, value))
78
+ return true;
79
+ }
80
+ }
81
+
82
+ // range ↔ range
83
+ if (w_bounds && q_bounds && overlaps(w_bounds, q_bounds))
84
+ return true;
85
+
86
+ return false;
87
+ }
88
+
89
+
90
+ // NOTE: Matching is based on intersection, not equality. A policy matches if, for every key in `when`, there exists at least one value that satisfies both the `when` constraint and the query constraint.
91
+ export function matchPolicy(query, when, g_obj) {
92
+ let obj = g_obj ?? {}
93
+ for (let key in when) {
94
+ if (key === "$and" || key === "$or") {
95
+ for (const element of when[key]) {
96
+ matchPolicy(query, element, obj)
97
+ }
98
+ } else if (key === "$ne" || key === "$nin" || key === "$not" || (when[key] && typeof when[key] === "object" && ("$ne" in when[key] || "$nin" in when[key] || "$not" in when[key])) ) {
99
+ throw new Error(`Error: Policy 'when' does not support negative constraints $ne, $nin and $not in Dip`)
100
+ // NOTE:
101
+ // Negative constraints are invalid for policy selection. Cost is additional suffix scans. But we disallow it so that it is immediately communicated
102
+ // There's no suffix you can eliminate from just knowing a negative. The safest thing is exactly to not let it influence policy selection.
103
+ } else {
104
+ obj[key] = obj[key] ?? []
105
+ obj[key].push(...entries(key, when))
106
+ }
107
+ }
108
+
109
+ // Only compare once, on the outermost call.
110
+ if (g_obj)
111
+ return;
112
+
113
+ for (let key in obj) {
114
+ obj[key] = [...new Set(obj[key])];
115
+
116
+ let w_values = obj[key]
117
+
118
+ let w_ranges = w_values.filter(item => typeof item === "object")
119
+ w_values = w_values.filter(item => typeof item !== "object")
120
+ const w_bounds = w_ranges.length ? extractBounds(reduceRanges(w_ranges)) : null
121
+
122
+ let q_values = entries(key, query)
123
+ let q_ranges = q_values.filter(item => typeof item === "object")
124
+ q_values = q_values.filter(item => typeof item !== "object")
125
+ const q_bounds = q_ranges.length ? extractBounds(reduceRanges(q_ranges)) : null
126
+
127
+ if (!intersects(w_values, w_bounds, q_values, q_bounds))
128
+ return false;
129
+
130
+ }
131
+ return true
132
+ }
133
+
134
+
135
+
136
+
137
+
138
+
139
+ export async function applySuffixPolicy(COLLECTIONS_JSON, collection, query) {
140
+ const collection_policy = COLLECTIONS_JSON[collection]?.policy ?? []
141
+ const suffixPolicies = typeof collection_policy === "object" && !Array.isArray(collection_policy) ? [collection_policy] : COLLECTIONS_JSON[collection]?.policy ?? []
142
+ let suffixes = []
143
+ for (const policy of suffixPolicies) {
144
+ if (matchPolicy(query, policy.when ?? {})) {
145
+ // generate suffixes
146
+ const array = await suffix(query, policy.suffix ?? [])
147
+ suffixes.push(...array)
148
+ }
149
+ }
150
+ suffixes = [...new Set(suffixes)];
151
+ return suffixes
152
+ }
153
+
154
+
155
+
156
+