@weborigami/async-tree 0.0.64-beta.2 → 0.0.64
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/package.json +2 -2
- package/src/FunctionTree.js +2 -2
- package/src/calendarTree.js +77 -0
- package/src/operations/cache.js +21 -6
- package/test/calendarTree.test.js +111 -0
- package/test/operations/cache.test.js +10 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@weborigami/async-tree",
|
|
3
|
-
"version": "0.0.64
|
|
3
|
+
"version": "0.0.64",
|
|
4
4
|
"description": "Asynchronous tree drivers based on standard JavaScript classes",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./main.js",
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
"typescript": "5.5.3"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@weborigami/types": "0.0.64
|
|
14
|
+
"@weborigami/types": "0.0.64"
|
|
15
15
|
},
|
|
16
16
|
"scripts": {
|
|
17
17
|
"test": "node --test --test-reporter=spec",
|
package/src/FunctionTree.js
CHANGED
|
@@ -27,11 +27,11 @@ export default class FunctionTree {
|
|
|
27
27
|
this.fn.length <= 1
|
|
28
28
|
? // Function takes no arguments, one argument, or a variable number of
|
|
29
29
|
// arguments: invoke it.
|
|
30
|
-
await this.fn.call(
|
|
30
|
+
await this.fn.call(this.parent, key)
|
|
31
31
|
: // Bind the key to the first parameter. Subsequent get calls will
|
|
32
32
|
// eventually bind all parameters until only one remains. At that point,
|
|
33
33
|
// the above condition will apply and the function will be invoked.
|
|
34
|
-
Reflect.construct(this.constructor, [this.fn.bind(
|
|
34
|
+
Reflect.construct(this.constructor, [this.fn.bind(this.parent, key)]);
|
|
35
35
|
setParent(value, this);
|
|
36
36
|
return value;
|
|
37
37
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return a tree of years, months, and days from a start date to an end date.
|
|
3
|
+
*
|
|
4
|
+
* Both the start and end date can be provided in "YYYY-MM-DD", "YYYY-MM", or
|
|
5
|
+
* "YYYY" format. If a start year is provided, but a month is not, then the
|
|
6
|
+
* first month of the year will be used; if a start month is provided, but a day
|
|
7
|
+
* is not, then the first day of that month will be used. Similar logic applies
|
|
8
|
+
* to the end date, using the last month of the year or the last day of the
|
|
9
|
+
* month.
|
|
10
|
+
*
|
|
11
|
+
* If a start date is omitted, today will be used, likewise for the end date.
|
|
12
|
+
*
|
|
13
|
+
* @param {string} [start] - Start date in "YYYY-MM-DD", "YYYY-MM", or "YYYY"
|
|
14
|
+
* format
|
|
15
|
+
* @param {string} [end] - End date in "YYYY-MM-DD", "YYYY-MM", or "YYYY" format
|
|
16
|
+
*/
|
|
17
|
+
export default function calendarTree(start, end) {
|
|
18
|
+
const startParts = start?.split("-") ?? [];
|
|
19
|
+
const endParts = end?.split("-") ?? [];
|
|
20
|
+
|
|
21
|
+
const today = new Date();
|
|
22
|
+
|
|
23
|
+
const startYear = startParts[0]
|
|
24
|
+
? parseInt(startParts[0])
|
|
25
|
+
: today.getFullYear();
|
|
26
|
+
const startMonth = startParts[1]
|
|
27
|
+
? parseInt(startParts[1])
|
|
28
|
+
: startParts[0]
|
|
29
|
+
? 1
|
|
30
|
+
: today.getMonth() + 1;
|
|
31
|
+
const startDay = startParts[2]
|
|
32
|
+
? parseInt(startParts[2])
|
|
33
|
+
: startParts[1]
|
|
34
|
+
? 1
|
|
35
|
+
: today.getDate();
|
|
36
|
+
|
|
37
|
+
const endYear = endParts[0] ? parseInt(endParts[0]) : today.getFullYear();
|
|
38
|
+
const endMonth = endParts[1]
|
|
39
|
+
? parseInt(endParts[1])
|
|
40
|
+
: endParts[0]
|
|
41
|
+
? 12
|
|
42
|
+
: today.getMonth() + 1;
|
|
43
|
+
const endDay = endParts[2]
|
|
44
|
+
? parseInt(endParts[2])
|
|
45
|
+
: endParts[1]
|
|
46
|
+
? daysInMonth(endYear, endMonth)
|
|
47
|
+
: today.getDate();
|
|
48
|
+
|
|
49
|
+
let years = {};
|
|
50
|
+
for (let year = startYear; year <= endYear; year++) {
|
|
51
|
+
let months = new Map();
|
|
52
|
+
const firstMonth = year === startYear ? startMonth : 1;
|
|
53
|
+
const lastMonth = year === endYear ? endMonth : 12;
|
|
54
|
+
for (let month = firstMonth; month <= lastMonth; month++) {
|
|
55
|
+
const monthPadded = month.toString().padStart(2, "0");
|
|
56
|
+
let days = new Map();
|
|
57
|
+
const firstDay =
|
|
58
|
+
year === startYear && month === startMonth ? startDay : 1;
|
|
59
|
+
const lastDay =
|
|
60
|
+
year === endYear && month === endMonth
|
|
61
|
+
? endDay
|
|
62
|
+
: daysInMonth(year, month);
|
|
63
|
+
for (let day = firstDay; day <= lastDay; day++) {
|
|
64
|
+
const dayPadded = day.toString().padStart(2, "0");
|
|
65
|
+
days.set(dayPadded, null);
|
|
66
|
+
}
|
|
67
|
+
months.set(monthPadded, days);
|
|
68
|
+
}
|
|
69
|
+
years[year] = months;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return years;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function daysInMonth(year, month) {
|
|
76
|
+
return new Date(year, month, 0).getDate();
|
|
77
|
+
}
|
package/src/operations/cache.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ObjectTree, Tree } from "../internal.js";
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Caches values from a source tree in a second cache tree. Cache source tree
|
|
@@ -14,16 +14,30 @@ import { DeepObjectTree, Tree } from "../internal.js";
|
|
|
14
14
|
* @typedef {import("../../index.ts").Treelike} Treelike
|
|
15
15
|
*
|
|
16
16
|
* @param {Treelike} sourceTreelike
|
|
17
|
-
* @param {AsyncMutableTree} [
|
|
17
|
+
* @param {AsyncMutableTree} [cacheTreelike]
|
|
18
18
|
* @param {Treelike} [filterTreelike]
|
|
19
19
|
* @returns {AsyncTree & { description: string }}
|
|
20
20
|
*/
|
|
21
|
-
export default function treeCache(
|
|
21
|
+
export default function treeCache(
|
|
22
|
+
sourceTreelike,
|
|
23
|
+
cacheTreelike,
|
|
24
|
+
filterTreelike
|
|
25
|
+
) {
|
|
22
26
|
const source = Tree.from(sourceTreelike);
|
|
23
27
|
const filter = filterTreelike ? Tree.from(filterTreelike) : undefined;
|
|
24
28
|
|
|
25
29
|
/** @type {AsyncMutableTree} */
|
|
26
|
-
|
|
30
|
+
let cache;
|
|
31
|
+
if (cacheTreelike) {
|
|
32
|
+
// @ts-ignore
|
|
33
|
+
cache = Tree.from(cacheTreelike);
|
|
34
|
+
if (!Tree.isAsyncMutableTree(cache)) {
|
|
35
|
+
throw new Error("Cache tree must define a set() method.");
|
|
36
|
+
}
|
|
37
|
+
} else {
|
|
38
|
+
cache = new ObjectTree({});
|
|
39
|
+
}
|
|
40
|
+
|
|
27
41
|
let keys;
|
|
28
42
|
return {
|
|
29
43
|
description: "cache",
|
|
@@ -47,8 +61,9 @@ export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
|
|
|
47
61
|
// Construct merged tree for a tree result.
|
|
48
62
|
if (cacheValue === undefined) {
|
|
49
63
|
// Construct new container in cache
|
|
50
|
-
|
|
51
|
-
cacheValue =
|
|
64
|
+
cacheValue = new ObjectTree({});
|
|
65
|
+
cacheValue.parent = this;
|
|
66
|
+
await cache.set(key, cacheValue);
|
|
52
67
|
}
|
|
53
68
|
value = treeCache(value, cacheValue, filterValue);
|
|
54
69
|
} else {
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import assert from "node:assert";
|
|
2
|
+
import { describe, test } from "node:test";
|
|
3
|
+
import { toPlainValue } from "../main.js";
|
|
4
|
+
import calendarTree from "../src/calendarTree.js";
|
|
5
|
+
|
|
6
|
+
describe("calendarTree", () => {
|
|
7
|
+
test("without a start or end, returns a tree for today", async () => {
|
|
8
|
+
const tree = calendarTree();
|
|
9
|
+
const plain = await toPlainValue(tree);
|
|
10
|
+
const today = new Date();
|
|
11
|
+
const year = today.getFullYear();
|
|
12
|
+
const month = (today.getMonth() + 1).toString().padStart(2, "0");
|
|
13
|
+
const day = today.getDate().toString().padStart(2, "0");
|
|
14
|
+
assert.deepEqual(plain, {
|
|
15
|
+
[year]: {
|
|
16
|
+
[month]: {
|
|
17
|
+
[day]: null,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("returns a tree for a month range", async () => {
|
|
24
|
+
const tree = calendarTree("2025-01", "2025-02");
|
|
25
|
+
const plain = await toPlainValue(tree);
|
|
26
|
+
assert.deepEqual(plain, {
|
|
27
|
+
2025: {
|
|
28
|
+
"01": {
|
|
29
|
+
"01": null,
|
|
30
|
+
"02": null,
|
|
31
|
+
"03": null,
|
|
32
|
+
"04": null,
|
|
33
|
+
"05": null,
|
|
34
|
+
"06": null,
|
|
35
|
+
"07": null,
|
|
36
|
+
"08": null,
|
|
37
|
+
"09": null,
|
|
38
|
+
10: null,
|
|
39
|
+
11: null,
|
|
40
|
+
12: null,
|
|
41
|
+
13: null,
|
|
42
|
+
14: null,
|
|
43
|
+
15: null,
|
|
44
|
+
16: null,
|
|
45
|
+
17: null,
|
|
46
|
+
18: null,
|
|
47
|
+
19: null,
|
|
48
|
+
20: null,
|
|
49
|
+
21: null,
|
|
50
|
+
22: null,
|
|
51
|
+
23: null,
|
|
52
|
+
24: null,
|
|
53
|
+
25: null,
|
|
54
|
+
26: null,
|
|
55
|
+
27: null,
|
|
56
|
+
28: null,
|
|
57
|
+
29: null,
|
|
58
|
+
30: null,
|
|
59
|
+
31: null,
|
|
60
|
+
},
|
|
61
|
+
"02": {
|
|
62
|
+
"01": null,
|
|
63
|
+
"02": null,
|
|
64
|
+
"03": null,
|
|
65
|
+
"04": null,
|
|
66
|
+
"05": null,
|
|
67
|
+
"06": null,
|
|
68
|
+
"07": null,
|
|
69
|
+
"08": null,
|
|
70
|
+
"09": null,
|
|
71
|
+
10: null,
|
|
72
|
+
11: null,
|
|
73
|
+
12: null,
|
|
74
|
+
13: null,
|
|
75
|
+
14: null,
|
|
76
|
+
15: null,
|
|
77
|
+
16: null,
|
|
78
|
+
17: null,
|
|
79
|
+
18: null,
|
|
80
|
+
19: null,
|
|
81
|
+
20: null,
|
|
82
|
+
21: null,
|
|
83
|
+
22: null,
|
|
84
|
+
23: null,
|
|
85
|
+
24: null,
|
|
86
|
+
25: null,
|
|
87
|
+
26: null,
|
|
88
|
+
27: null,
|
|
89
|
+
28: null,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
test("returns a tree for a day range", async () => {
|
|
96
|
+
const tree = calendarTree("2025-02-27", "2025-03-02");
|
|
97
|
+
const plain = await toPlainValue(tree);
|
|
98
|
+
assert.deepEqual(plain, {
|
|
99
|
+
2025: {
|
|
100
|
+
"02": {
|
|
101
|
+
27: null,
|
|
102
|
+
28: null,
|
|
103
|
+
},
|
|
104
|
+
"03": {
|
|
105
|
+
"01": null,
|
|
106
|
+
"02": null,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
});
|
|
111
|
+
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import assert from "node:assert";
|
|
2
2
|
import { describe, test } from "node:test";
|
|
3
|
-
import { ObjectTree, Tree } from "../../src/internal.js";
|
|
3
|
+
import { DeepObjectTree, ObjectTree, Tree } from "../../src/internal.js";
|
|
4
4
|
import cache from "../../src/operations/cache.js";
|
|
5
5
|
|
|
6
6
|
describe("cache", () => {
|
|
7
7
|
test("caches reads of values from one tree into another", async () => {
|
|
8
8
|
const objectCache = new ObjectTree({});
|
|
9
9
|
const fixture = cache(
|
|
10
|
-
|
|
10
|
+
new DeepObjectTree({
|
|
11
11
|
a: 1,
|
|
12
12
|
b: 2,
|
|
13
13
|
c: 3,
|
|
@@ -18,7 +18,7 @@ describe("cache", () => {
|
|
|
18
18
|
objectCache
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
-
const keys =
|
|
21
|
+
const keys = [...(await fixture.keys())];
|
|
22
22
|
assert.deepEqual(keys, ["a", "b", "c", "more"]);
|
|
23
23
|
|
|
24
24
|
assert.equal(await objectCache.get("a"), undefined);
|
|
@@ -28,6 +28,13 @@ describe("cache", () => {
|
|
|
28
28
|
assert.equal(await objectCache.get("b"), undefined);
|
|
29
29
|
assert.equal(await fixture.get("b"), 2);
|
|
30
30
|
assert.equal(await objectCache.get("b"), 2);
|
|
31
|
+
|
|
32
|
+
assert.equal(await objectCache.get("more"), undefined);
|
|
33
|
+
const more = await fixture.get("more");
|
|
34
|
+
assert.deepEqual([...(await more.keys())], ["d"]);
|
|
35
|
+
assert.equal(await more.get("d"), 4);
|
|
36
|
+
const moreCache = await objectCache.get("more");
|
|
37
|
+
assert.equal(await moreCache.get("d"), 4);
|
|
31
38
|
});
|
|
32
39
|
|
|
33
40
|
test("if a cache filter is supplied, it only caches values whose keys match the filter", async () => {
|