@aspan-corporation/ac-shared 1.2.30 → 1.2.31

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,53 @@
1
+ /**
2
+ * Diary entries are Markdown files stored in a dedicated diary bucket at keys
3
+ * `diary/YYYY/MM/YYYYMMDD.md`. They are indexed in the shared metadata table so
4
+ * they are searchable through the same interface as photos. Body text is made
5
+ * searchable by tokenising it into `ac:text:<word>` tags that ride the existing
6
+ * inverted-index search table.
7
+ */
8
+ /** Key prefix for every diary object (and the `folder`/id namespace). */
9
+ export declare const DIARY_PREFIX = "diary/";
10
+ /** Marks a meta item as a diary entry (value "true"). */
11
+ export declare const TAG_DIARY_ENTRY = "ac:diary:entry";
12
+ /** Human title of the entry. */
13
+ export declare const TAG_DIARY_TITLE = "ac:diary:title";
14
+ /** Short body preview for card display (first ~160 chars, no markdown). */
15
+ export declare const TAG_DIARY_PREVIEW = "ac:diary:preview";
16
+ /** One per embedded photo: value is the referenced media key. */
17
+ export declare const TAG_DIARY_PHOTO = "ac:diary:photo";
18
+ /** Namespace for body word tokens: `ac:text:<word>`. */
19
+ export declare const TEXT_TOKEN_PREFIX = "ac:text:";
20
+ /** Max body characters surfaced in the preview tag. */
21
+ export declare const DIARY_PREVIEW_LENGTH = 160;
22
+ /** Upper bound on distinct word tokens written per entry (bounds item size). */
23
+ export declare const MAX_TEXT_TOKENS = 400;
24
+ /**
25
+ * Compute the diary object key for a date.
26
+ * new Date("2026-06-15") → "diary/2026/06/20260615.md"
27
+ * Accepts a Date or an ISO/`YYYY-MM-DD` string. Uses UTC parts so the key is
28
+ * stable regardless of the runtime timezone.
29
+ */
30
+ export declare const diaryKey: (date: Date | string) => string;
31
+ /** True when an id/key is a diary entry object. */
32
+ export declare const isDiaryKey: (key: string) => boolean;
33
+ /**
34
+ * Parse the date out of a diary key (the inverse of `diaryKey`).
35
+ * "diary/2026/06/20260615.md" → { year: 2026, month: 6, day: 15 }
36
+ * Returns undefined when the key isn't a recognised diary key.
37
+ */
38
+ export declare const parseDiaryKeyDate: (key: string) => {
39
+ year: number;
40
+ month: number;
41
+ day: number;
42
+ } | undefined;
43
+ /**
44
+ * Tokenise diary markdown into a deduplicated set of searchable lowercase
45
+ * words. Strips markdown syntax (code fences, image/link targets, formatting),
46
+ * drops stop-words and tokens shorter than 2 chars, and caps the result at
47
+ * `MAX_TEXT_TOKENS` to bound the meta-item size. No stemming (exact-word match).
48
+ */
49
+ export declare const tokenizeText: (markdown: string) => string[];
50
+ /** Plain-text preview: strip markdown, collapse whitespace, truncate. */
51
+ export declare const diaryPreview: (markdown: string) => string;
52
+ /** Extract the media keys of photos embedded as `![alt](mediaKey)` in the body. */
53
+ export declare const extractEmbeddedPhotoKeys: (markdown: string) => string[];
@@ -0,0 +1,125 @@
1
+ /**
2
+ * Diary entries are Markdown files stored in a dedicated diary bucket at keys
3
+ * `diary/YYYY/MM/YYYYMMDD.md`. They are indexed in the shared metadata table so
4
+ * they are searchable through the same interface as photos. Body text is made
5
+ * searchable by tokenising it into `ac:text:<word>` tags that ride the existing
6
+ * inverted-index search table.
7
+ */
8
+ /** Key prefix for every diary object (and the `folder`/id namespace). */
9
+ export const DIARY_PREFIX = "diary/";
10
+ /** Marks a meta item as a diary entry (value "true"). */
11
+ export const TAG_DIARY_ENTRY = "ac:diary:entry";
12
+ /** Human title of the entry. */
13
+ export const TAG_DIARY_TITLE = "ac:diary:title";
14
+ /** Short body preview for card display (first ~160 chars, no markdown). */
15
+ export const TAG_DIARY_PREVIEW = "ac:diary:preview";
16
+ /** One per embedded photo: value is the referenced media key. */
17
+ export const TAG_DIARY_PHOTO = "ac:diary:photo";
18
+ /** Namespace for body word tokens: `ac:text:<word>`. */
19
+ export const TEXT_TOKEN_PREFIX = "ac:text:";
20
+ /** Max body characters surfaced in the preview tag. */
21
+ export const DIARY_PREVIEW_LENGTH = 160;
22
+ /** Upper bound on distinct word tokens written per entry (bounds item size). */
23
+ export const MAX_TEXT_TOKENS = 400;
24
+ const pad2 = (n) => String(n).padStart(2, "0");
25
+ /**
26
+ * Compute the diary object key for a date.
27
+ * new Date("2026-06-15") → "diary/2026/06/20260615.md"
28
+ * Accepts a Date or an ISO/`YYYY-MM-DD` string. Uses UTC parts so the key is
29
+ * stable regardless of the runtime timezone.
30
+ */
31
+ export const diaryKey = (date) => {
32
+ const d = typeof date === "string" ? new Date(date) : date;
33
+ if (isNaN(d.getTime())) {
34
+ throw new Error(`diaryKey: invalid date "${String(date)}"`);
35
+ }
36
+ const y = d.getUTCFullYear();
37
+ const m = pad2(d.getUTCMonth() + 1);
38
+ const day = pad2(d.getUTCDate());
39
+ return `${DIARY_PREFIX}${y}/${m}/${y}${m}${day}.md`;
40
+ };
41
+ /** True when an id/key is a diary entry object. */
42
+ export const isDiaryKey = (key) => key.startsWith(DIARY_PREFIX) && key.endsWith(".md");
43
+ /**
44
+ * Parse the date out of a diary key (the inverse of `diaryKey`).
45
+ * "diary/2026/06/20260615.md" → { year: 2026, month: 6, day: 15 }
46
+ * Returns undefined when the key isn't a recognised diary key.
47
+ */
48
+ export const parseDiaryKeyDate = (key) => {
49
+ const m = /diary\/(\d{4})\/(\d{2})\/(\d{4})(\d{2})(\d{2})\.md$/.exec(key);
50
+ if (!m)
51
+ return undefined;
52
+ return { year: Number(m[3]), month: Number(m[4]), day: Number(m[5]) };
53
+ };
54
+ // A small English stop-word set — high-frequency words that add noise and bloat
55
+ // the index without improving recall for a personal diary.
56
+ const STOP_WORDS = new Set([
57
+ "the", "and", "for", "are", "but", "not", "you", "all", "any", "can", "had",
58
+ "her", "was", "one", "our", "out", "day", "get", "has", "him", "his", "how",
59
+ "its", "may", "new", "now", "old", "see", "two", "way", "who", "did", "yes",
60
+ "his", "she", "they", "them", "this", "that", "with", "have", "from", "your",
61
+ "were", "been", "their", "what", "when", "then", "than", "into", "just",
62
+ "like", "over", "also", "back", "after", "would", "could", "there", "here",
63
+ "about", "which", "while", "these", "those", "where", "very", "much", "some",
64
+ "such", "only", "more", "most", "will", "well", "went", "going", "got",
65
+ ]);
66
+ /**
67
+ * Tokenise diary markdown into a deduplicated set of searchable lowercase
68
+ * words. Strips markdown syntax (code fences, image/link targets, formatting),
69
+ * drops stop-words and tokens shorter than 2 chars, and caps the result at
70
+ * `MAX_TEXT_TOKENS` to bound the meta-item size. No stemming (exact-word match).
71
+ */
72
+ export const tokenizeText = (markdown) => {
73
+ if (!markdown)
74
+ return [];
75
+ const stripped = markdown
76
+ // fenced + inline code
77
+ .replace(/```[\s\S]*?```/g, " ")
78
+ .replace(/`[^`]*`/g, " ")
79
+ // image / link targets: keep the visible text, drop the URL/key
80
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, " $1 ")
81
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, " $1 ")
82
+ // leftover markdown punctuation
83
+ .replace(/[#>*_~\-]+/g, " ");
84
+ const seen = new Set();
85
+ for (const raw of stripped.toLowerCase().split(/[^a-z0-9]+/)) {
86
+ if (raw.length < 2)
87
+ continue;
88
+ // Drop pure numbers (dates/counts add noise); keep alphanumerics like "v2".
89
+ if (/^\d+$/.test(raw))
90
+ continue;
91
+ if (STOP_WORDS.has(raw))
92
+ continue;
93
+ seen.add(raw);
94
+ if (seen.size >= MAX_TEXT_TOKENS)
95
+ break;
96
+ }
97
+ return [...seen];
98
+ };
99
+ /** Plain-text preview: strip markdown, collapse whitespace, truncate. */
100
+ export const diaryPreview = (markdown) => {
101
+ const text = markdown
102
+ .replace(/```[\s\S]*?```/g, " ")
103
+ .replace(/`[^`]*`/g, " ")
104
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, " ")
105
+ .replace(/\[([^\]]*)\]\([^)]*\)/g, " $1 ")
106
+ .replace(/[#>*_~]+/g, " ")
107
+ .replace(/\s+/g, " ")
108
+ .trim();
109
+ return text.length > DIARY_PREVIEW_LENGTH
110
+ ? text.slice(0, DIARY_PREVIEW_LENGTH).trimEnd() + "…"
111
+ : text;
112
+ };
113
+ /** Extract the media keys of photos embedded as `![alt](mediaKey)` in the body. */
114
+ export const extractEmbeddedPhotoKeys = (markdown) => {
115
+ const keys = new Set();
116
+ const re = /!\[[^\]]*\]\(([^)]+)\)/g;
117
+ let m;
118
+ while ((m = re.exec(markdown)) !== null) {
119
+ const key = m[1].trim();
120
+ // Only treat library media keys as photo links (ignore external URLs).
121
+ if (key && !/^https?:\/\//i.test(key))
122
+ keys.add(key);
123
+ }
124
+ return [...keys];
125
+ };
@@ -4,3 +4,4 @@ export * from "./thumbsKey.js";
4
4
  export * from "./helpers.js";
5
5
  export * from "./processMeta.js";
6
6
  export * from "./parseFolderDate.js";
7
+ export * from "./diary.js";
@@ -8,3 +8,4 @@ export * from "./thumbsKey.js";
8
8
  export * from "./helpers.js";
9
9
  export * from "./processMeta.js";
10
10
  export * from "./parseFolderDate.js";
11
+ export * from "./diary.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aspan-corporation/ac-shared",
3
- "version": "1.2.30",
3
+ "version": "1.2.31",
4
4
  "description": "",
5
5
  "keywords": [],
6
6
  "exports": {
@@ -23,6 +23,10 @@
23
23
  "./utils/thumbsKey": {
24
24
  "types": "./lib/utils/thumbsKey.d.ts",
25
25
  "import": "./lib/utils/thumbsKey.js"
26
+ },
27
+ "./utils/diary": {
28
+ "types": "./lib/utils/diary.d.ts",
29
+ "import": "./lib/utils/diary.js"
26
30
  }
27
31
  },
28
32
  "author": "",