corebasic 1.0.212 → 1.0.214

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,99 @@
1
+ import { entries, reduceRanges, extractBounds, getNormalizedBounds } from './index.js';
2
+ import { formatDate, fillDates } from './date.js';
3
+ const EXPLICIT_SUFFIX_POLICY_TYPES = new Set(["string", "date", "number"]);
4
+ export async function suffix(doc, policies, insertMode, arg) {
5
+ let suffixes = [""];
6
+ for (const policy of policies) {
7
+ const value = typeof policy.value === "string" ? policy.value.trim() : undefined;
8
+ if ("value" in policy && !value)
9
+ throw new Error(`Error: Invalid suffix policy fixed 'value' specified for key in Dip`);
10
+ let keys = value ? [value] : entries(policy.key, doc);
11
+ const [policy_type, format] = (policy.type ?? "string").split(':');
12
+ if (!EXPLICIT_SUFFIX_POLICY_TYPES.has(policy_type))
13
+ throw new Error(`Error: Invalid suffix policy type specified for key ${policy.key} in Dip`);
14
+ if (policy_type === "string" && keys.filter(item => typeof item === "object").length)
15
+ throw new Error(`Error: Invalid numeric bounds on string type in suffix policy specified for key ${policy.key} in Dip`);
16
+ if (policy_type === "date") {
17
+ if (!format?.trim())
18
+ throw new Error(`Error: Invalid date format in suffix policy type specified for key ${policy.key} in Dip`);
19
+ if (format.includes('/'))
20
+ throw new Error(`Error: Invalid date format containing '/' in suffix policy type specified for key ${policy.key} in Dip`);
21
+ }
22
+ const ls_threshold = policy.ls_threshold ?? 5000;
23
+ // ====== Range ======
24
+ let ranges = keys.filter(item => typeof item === "object");
25
+ keys = keys.filter(item => typeof item !== "object");
26
+ if (policy_type === "date") {
27
+ keys = keys.map(key => formatDate(key, format));
28
+ if (ranges.length) {
29
+ ranges = extractBounds(reduceRanges(ranges));
30
+ const from = ranges.from ?? policy.min; // policy.min is always inclusive i.e $gte
31
+ const to = ranges.to ?? policy.max; // policy.max is always inclusive i.e $lte
32
+ const fromOp = ranges.from ? ranges.fromOp : "$gte"; // policy.min is $gte
33
+ const toOp = ranges.to ? ranges.toOp : "$lte"; // policy.max is $lte
34
+ if (from === undefined || to === undefined) {
35
+ keys = []; // Fall back to Dip.operation("ls").
36
+ console.warn(`Warn: Missing suffix policy min/max bound for ${policy_type} range specified for key ${policy.key} in Dip`);
37
+ }
38
+ else {
39
+ ranges = fillDates({ from, to, fromOp, toOp }, format);
40
+ if (!ranges.length && policy.inferRange)
41
+ ranges = [...new Set([from === undefined ? undefined : formatDate(from, format), to === undefined ? undefined : formatDate(to, format)].filter(item => item))];
42
+ // console.log(ranges)
43
+ if (ranges.length > ls_threshold) {
44
+ keys = []; // Too many suffixes to enumerate. Fall back to Dip.operation("ls").
45
+ console.warn(`Warn: Suffix ls_threshold exceeded for key ${policy.key} in Dip`);
46
+ }
47
+ else
48
+ keys.push(...ranges);
49
+ }
50
+ }
51
+ keys = [...new Set(keys)];
52
+ }
53
+ else if (policy_type === "number" && ranges.length) {
54
+ ranges = getNormalizedBounds(extractBounds(reduceRanges(ranges)), "number");
55
+ const from = ranges.from ?? policy.min; // policy.min is always inclusive i.e $gte
56
+ const to = ranges.to ?? policy.max; // policy.max is always inclusive i.e $lte
57
+ if (from === undefined || to === undefined) {
58
+ keys = []; // Fall back to Dip.operation("ls").
59
+ console.warn(`Warn: Missing suffix policy min/max bound for ${policy_type} range specified for key ${policy.key} in Dip`);
60
+ }
61
+ else {
62
+ if (to - from + 1 > ls_threshold) {
63
+ keys = []; // Too many suffixes to enumerate. Fall back to Dip.operation("ls").
64
+ console.warn(`Warn: Suffix ls_threshold exceeded for key ${policy.key} in Dip`);
65
+ }
66
+ else {
67
+ for (let i = from; i <= to; i++)
68
+ keys.push(i);
69
+ }
70
+ keys = [...new Set(keys)];
71
+ }
72
+ }
73
+ // =================
74
+ let ls = false;
75
+ let suffixAssociatedKeys = {};
76
+ if (!keys.length && insertMode)
77
+ throw new Error(`Error: Missing suffix policy key ${policy.key} in Dip`);
78
+ if (!keys.length && !insertMode) {
79
+ ls = true;
80
+ for (const suffix of suffixes) {
81
+ const dirs = (await Dip.operation("ls", { db: arg.db, collection: arg.collection, suffix })).filter(dir => !dir.startsWith('chunk-'));
82
+ suffixAssociatedKeys[suffix] = dirs;
83
+ }
84
+ 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.`);
85
+ }
86
+ if (!ls && keys.some(key => typeof key !== "number" && typeof key !== "string")) // Dates are already string by this point
87
+ throw new Error(`Error: Invalid value for suffix policy key ${policy.key} in Dip`);
88
+ // =================
89
+ const next = [];
90
+ for (const suffix of suffixes) {
91
+ const currentKeys = ls ? suffixAssociatedKeys[suffix] : keys;
92
+ for (const key of currentKeys) {
93
+ next.push(`${suffix}/${key}`);
94
+ }
95
+ }
96
+ suffixes = next; // The "dead branches" disappear automatically because they never contribute any children.
97
+ }
98
+ return suffixes.length === 1 && suffixes[0] === "" ? [] : suffixes;
99
+ }
@@ -0,0 +1,187 @@
1
+ import { describe, test } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { formatDate, fillDates } from "../date.js";
4
+ describe("formatDate()", () => {
5
+ test("formats full date correctly", () => {
6
+ const date = new Date(2026, 6, 9);
7
+ assert.equal(formatDate(date, "YYYY-MM-DD"), "2026-07-09");
8
+ });
9
+ test("formats all tokens", () => {
10
+ const date = new Date(2026, 6, 9);
11
+ assert.equal(formatDate(date, "YYYY YY MMMM MMM MM DD D"), "2026 26 July Jul 07 09 9");
12
+ });
13
+ test("formats month names", () => {
14
+ const date = new Date(2026, 0, 15);
15
+ assert.equal(formatDate(date, "MMMM"), "January");
16
+ assert.equal(formatDate(date, "MMM"), "Jan");
17
+ });
18
+ test("formats december correctly", () => {
19
+ const date = new Date(2026, 11, 31);
20
+ assert.equal(formatDate(date, "MMMM MMM"), "December Dec");
21
+ });
22
+ test("pads single digit days", () => {
23
+ const date = new Date(2026, 6, 1);
24
+ assert.equal(formatDate(date, "DD"), "01");
25
+ });
26
+ test("does not pad D token", () => {
27
+ const date = new Date(2026, 6, 1);
28
+ assert.equal(formatDate(date, "D"), "1");
29
+ });
30
+ test("pads single digit months", () => {
31
+ const date = new Date(2026, 0, 5);
32
+ assert.equal(formatDate(date, "MM"), "01");
33
+ });
34
+ test("handles Date object input", () => {
35
+ const date = new Date(2026, 5, 20);
36
+ assert.equal(formatDate(date, "YYYY-MM-DD"), "2026-06-20");
37
+ });
38
+ test("handles timestamp input", () => {
39
+ const timestamp = new Date(2026, 5, 20).getTime();
40
+ assert.equal(formatDate(timestamp, "YYYY-MM-DD"), "2026-06-20");
41
+ });
42
+ test("handles ISO string input", () => {
43
+ assert.equal(formatDate("2026-07-09", "YYYY-MM-DD"), "2026-07-09");
44
+ });
45
+ test("handles arbitrary separators", () => {
46
+ const date = new Date(2026, 6, 9);
47
+ assert.equal(formatDate(date, "DD/MM/YYYY"), "09/07/2026");
48
+ assert.equal(formatDate(date, "D-MMM-YY"), "9-Jul-26");
49
+ });
50
+ test("throws for invalid date string", () => {
51
+ assert.throws(() => formatDate("invalid-date", "YYYY-MM-DD"));
52
+ });
53
+ test("throws for invalid timestamp", () => {
54
+ assert.throws(() => formatDate(NaN, "YYYY"));
55
+ });
56
+ test("handles leap day", () => {
57
+ const date = new Date(2024, 1, 29);
58
+ assert.equal(formatDate(date, "YYYY-MM-DD"), "2024-02-29");
59
+ });
60
+ test("handles year boundary", () => {
61
+ const date = new Date(2025, 11, 31);
62
+ assert.equal(formatDate(date, "YYYY-MM-DD"), "2025-12-31");
63
+ });
64
+ test("replaces longer tokens before shorter tokens", () => {
65
+ const date = new Date(2026, 6, 9);
66
+ assert.equal(formatDate(date, "MMMM MMM MM"), "July Jul 07");
67
+ });
68
+ test("keeps unknown characters untouched", () => {
69
+ const date = new Date(2026, 6, 9);
70
+ assert.equal(formatDate(date, "Today is DD-th"), "Today is 09-th");
71
+ });
72
+ });
73
+ describe("fillDates()", () => {
74
+ test("fills daily dates", () => {
75
+ assert.deepEqual(fillDates({ from: "2026-07-01", to: "2026-07-05", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD"), [
76
+ "2026-07-01",
77
+ "2026-07-02",
78
+ "2026-07-03",
79
+ "2026-07-04",
80
+ "2026-07-05",
81
+ ]);
82
+ });
83
+ test("fills monthly dates", () => {
84
+ assert.deepEqual(fillDates({ from: "2026-01-01", to: "2026-05-01", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM"), [
85
+ "2026-01",
86
+ "2026-02",
87
+ "2026-03",
88
+ "2026-04",
89
+ "2026-05",
90
+ ]);
91
+ });
92
+ test("fills yearly dates", () => {
93
+ assert.deepEqual(fillDates({ from: "2020-01-01", to: "2024-01-01", fromOp: "$gte", toOp: "$lte" }, "YYYY"), [
94
+ "2020",
95
+ "2021",
96
+ "2022",
97
+ "2023",
98
+ "2024",
99
+ ]);
100
+ });
101
+ test("returns one value for same date", () => {
102
+ assert.deepEqual(fillDates({ from: "2026-07-01", to: "2026-07-01", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD"), [
103
+ "2026-07-01"
104
+ ]);
105
+ });
106
+ test("returns empty for reversed range", () => {
107
+ assert.deepEqual(fillDates({ from: "2026-07-10", to: "2026-07-01", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD"), []);
108
+ });
109
+ test("removes duplicates when format loses precision", () => {
110
+ assert.deepEqual(fillDates({ from: "2026-01-01", to: "2026-01-31", fromOp: "$gte", toOp: "$lte" }, "MMMM"), [
111
+ "January"
112
+ ]);
113
+ });
114
+ test("handles leap year traversal", () => {
115
+ assert.deepEqual(fillDates({ from: "2024-02-28", to: "2024-03-01", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD"), [
116
+ "2024-02-28",
117
+ "2024-02-29",
118
+ "2024-03-01",
119
+ ]);
120
+ });
121
+ test("handles year rollover", () => {
122
+ assert.deepEqual(fillDates({ from: "2025-12-30", to: "2026-01-02", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD"), [
123
+ "2025-12-30",
124
+ "2025-12-31",
125
+ "2026-01-01",
126
+ "2026-01-02",
127
+ ]);
128
+ });
129
+ test("fills across years using months", () => {
130
+ assert.deepEqual(fillDates({ from: "2025-11-01", to: "2026-02-01", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM"), [
131
+ "2025-11",
132
+ "2025-12",
133
+ "2026-01",
134
+ "2026-02",
135
+ ]);
136
+ });
137
+ test("large yearly range", () => {
138
+ const result = fillDates({ from: "2000-01-01", to: "2020-01-01", fromOp: "$gte", toOp: "$lte" }, "YYYY");
139
+ assert.equal(result.length, 21);
140
+ });
141
+ test("large daily range contains no duplicates", () => {
142
+ const result = fillDates({ from: "2026-01-01", to: "2026-12-31", fromOp: "$gte", toOp: "$lte" }, "YYYY-MM-DD");
143
+ assert.equal(new Set(result).size, result.length);
144
+ });
145
+ test("excludes lower bound with $gt", () => {
146
+ assert.deepEqual(fillDates({
147
+ from: "2026-07-01",
148
+ to: "2026-07-03",
149
+ fromOp: "$gt",
150
+ toOp: null,
151
+ }, "YYYY-MM-DD"), [
152
+ "2026-07-02",
153
+ "2026-07-03",
154
+ ]);
155
+ });
156
+ test("excludes upper bound with $lt", () => {
157
+ assert.deepEqual(fillDates({
158
+ from: "2026-07-01",
159
+ to: "2026-07-03",
160
+ fromOp: null,
161
+ toOp: "$lt",
162
+ }, "YYYY-MM-DD"), [
163
+ "2026-07-01",
164
+ "2026-07-02",
165
+ ]);
166
+ });
167
+ test("excludes both bounds", () => {
168
+ assert.deepEqual(fillDates({
169
+ from: "2026-07-01",
170
+ to: "2026-07-05",
171
+ fromOp: "$gt",
172
+ toOp: "$lt",
173
+ }, "YYYY-MM-DD"), [
174
+ "2026-07-02",
175
+ "2026-07-03",
176
+ "2026-07-04",
177
+ ]);
178
+ });
179
+ test("returns empty when exclusive bounds collapse range", () => {
180
+ assert.deepEqual(fillDates({
181
+ from: "2026-07-01",
182
+ to: "2026-07-02",
183
+ fromOp: "$gt",
184
+ toOp: "$lt",
185
+ }, "YYYY-MM-DD"), []);
186
+ });
187
+ });