@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weborigami/async-tree",
3
- "version": "0.0.64-beta.2",
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-beta.2"
14
+ "@weborigami/types": "0.0.64"
15
15
  },
16
16
  "scripts": {
17
17
  "test": "node --test --test-reporter=spec",
@@ -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(null, key)
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(null, key)]);
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
+ }
@@ -1,4 +1,4 @@
1
- import { DeepObjectTree, Tree } from "../internal.js";
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} [cacheTree]
17
+ * @param {AsyncMutableTree} [cacheTreelike]
18
18
  * @param {Treelike} [filterTreelike]
19
19
  * @returns {AsyncTree & { description: string }}
20
20
  */
21
- export default function treeCache(sourceTreelike, cacheTree, filterTreelike) {
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
- const cache = cacheTree ?? new DeepObjectTree({});
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
- await cache.set(key, {});
51
- cacheValue = await cache.get(key);
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
- Tree.from({
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 = Array.from(await fixture.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 () => {