sensemaking 0.13.0 → 0.13.1

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": "sensemaking",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
4
4
  "description": "Query and search your markdown notes with context-aware progressive disclosure: SQL over frontmatter, links, and text, plus semantic search and link-graph ranking. No server, no build step",
5
5
  "keywords": [
6
6
  "markdown",
@@ -35,8 +35,8 @@ SELECT basename(f.path, '.md') AS name, f.year, f.scoreImdb, f.runtime, f.direct
35
35
  FROM frontmatter f
36
36
  WHERE EXISTS (SELECT 1 FROM json_each(f.categories) WHERE value = '[[Movies]]')
37
37
  AND instr(basename(f.path), 'Template') = 0
38
- AND (f.last IS NULL OR f.last = '[null]')
39
- AND (f.rating IS NULL OR f.rating = '[null]')
38
+ AND (f.last IS NULL OR f.last IN ('', '[]', '[null]'))
39
+ AND (f.rating IS NULL OR f.rating IN ('', '[]', '[null]'))
40
40
  ORDER BY f.scoreImdb DESC, name ASC
41
41
  ```
42
42
 
@@ -47,22 +47,27 @@ times have this shape; creation times share it.
47
47
  | `==` `!=` `>` `<` `>=` `<=` | same |
48
48
  | `&&` `\|\|` `!` | `AND` `OR` `NOT` |
49
49
  | `and:` / `or:` / `not:` filter blocks | parenthesized `AND` / `OR` / `NOT` |
50
- | `field.isEmpty()` | `(f.field IS NULL OR f.field = '[null]')` |
50
+ | `field.isEmpty()` | `(f.field IS NULL OR f.field IN ('', '[]', '[null]'))` |
51
51
  | `file.hasTag("book")` | `EXISTS (SELECT 1 FROM tags WHERE tags.path = f.path AND (tag = 'book' OR tag LIKE 'book/%'))` |
52
52
  | `file.hasLink("Note")` | `EXISTS (SELECT 1 FROM links WHERE src = f.path AND dst = 'Note.md')` |
53
53
  | `list.contains(x)` | `EXISTS (SELECT 1 FROM json_each(f.list) WHERE value = x)` |
54
54
  | `string.contains(x)` | `instr(f.string, x) > 0` |
55
55
  | `list.containsAny(...)` | the `json_each` EXISTS with `value IN (...)` |
56
56
  | `/regex/.matches(x)` | no SQLite regex; `LIKE`/`GLOB` cover anchored and wildcard shapes |
57
+ | `value.isType("object")` | `json_each`'s own `type` column: `... FROM json_each(f.field) j WHERE j.type = 'object'` |
57
58
 
58
59
  A field no note in the tree declares has no column at all, so a filter naming it errors with
59
60
  `no such column` instead of treating every row as empty. Obsidian's evaluator returns empty
60
61
  for unknown properties; SQL does not. Dropping the clause states the same thing the error
61
62
  did, and `SELECT name FROM pragma_table_info('frontmatter')` lists what exists.
62
63
 
63
- `isEmpty()`'s second arm exists because a list key written with no items (`tags:` above a bare
64
- `-`) stores as the JSON text `[null]`: present, not NULL, holding nothing. Obsidian's
65
- `isEmpty()` is true for it; `IS NULL` alone is not.
64
+ `isEmpty()` has four true cases because empty is stored three ways: NULL (key absent), `''`
65
+ (empty string value), `'[]'` (a list written `[]`), and `'[null]'` (a list key above a bare
66
+ `-`). Obsidian's `isEmpty()` is true for all of them; `IS NULL` alone finds only the first.
67
+ The IN list is the whole test -- `json_array_length()` is not: it reads `'[null]'` as length
68
+ 1 and throws on plain strings. The same trap inside a list: `json_each` hands a string member
69
+ to `value` as plain text, so `json_type(value)` throws `malformed JSON` on it; the scan's own
70
+ `type` column is the discriminator.
66
71
 
67
72
  `contains(link("Movies"))` compares against a link value. In frontmatter, a list of links
68
73
  holds the written text, so the `json_each` comparison value is the literal `[[Movies]]`.
@@ -94,6 +99,12 @@ Formulas are SELECT expressions. The Bases functions map onto SQLite's:
94
99
  Bases durations are typed; SQL date arithmetic is julianday day-fractions. A formula chaining
95
100
  duration fields (`.days.round()`) flattens to arithmetic on the julianday difference.
96
101
 
102
+ A formula referencing another formula becomes a CTE layer: SQL cannot read a SELECT alias in
103
+ the same SELECT list, so each dependency level computes its formulas as columns and the next
104
+ level reads them (`WITH t AS (SELECT ..., <level-1 formulas> FROM frontmatter) SELECT ...,
105
+ <level-2 formulas> FROM t`). A five-formula chain is however many *levels* it has, not five
106
+ CTEs -- formulas that only read base columns share one layer.
107
+
97
108
  ## Views
98
109
 
99
110
  - `sort:` (multi-key, each with direction) -> `ORDER BY a DESC, b ASC`. `limit:` -> `LIMIT`.