Skip to content

Commit aeac686

Browse files
committed
Fix evaluate and additional codeblock documentation
1 parent cf05a78 commit aeac686

3 files changed

Lines changed: 317 additions & 20 deletions

File tree

docs/docs/code-views/local-api.md

Lines changed: 305 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,11 @@ return function View() {
2323
```
2424
~~~
2525

26-
## Fetching Data
26+
## Query Hooks
2727

2828
Datacore provides several methods for querying for data, including by the full query language and by path explicitly.
2929

30-
#### `dc.useCurrentFile()`
30+
### `dc.useCurrentFile()`
3131

3232
Loads the metadata for the file that the view is in - this will usually be `MarkdownPage`, but can also be a `CanvasPage`.
3333
Using this hook will automatically refresh the view whenever the current file changes.
@@ -48,7 +48,7 @@ should update via the `debounce` property.
4848
const file = dc.useCurrentFile({ debounce: 10000 });
4949
```
5050

51-
#### `dc.useCurrentPath()`
51+
### `dc.useCurrentPath()`
5252

5353
Loads the path of the file that the view is in - this will usually be `MarkdownPage`, but can also be a `CanvasPage`.
5454
Using this hook will automatically refresh the view whenever the current file changes.
@@ -68,7 +68,7 @@ Like `useCurrentFile`, `dc.useCurrentPath` accepts an optional settings argument
6868
const path = dc.useCurrentPath({ debounce: 10000 });
6969
```
7070

71-
#### `dc.useQuery()`
71+
### `dc.useQuery()`
7272

7373
Query for a list of results using the [query language](/data/query). This will return a vanilla javascript list containing
7474
all of the results that match the query, which can be a wide range of different data types. This hook will cause the view
@@ -94,8 +94,7 @@ return function View() {
9494
}
9595
```
9696

97-
98-
#### `dc.useFullQuery()`
97+
### `dc.useFullQuery()`
9998

10099
Variant of `dc.useQuery` which returns a full search result object, which mainly provides a bit of useful extra metadata about how the
101100
search performed. Specifically, it returns the following data:
@@ -127,7 +126,7 @@ return function View() {
127126
}
128127
```
129128

130-
#### `dc.useIndexUpdates()`
129+
### `dc.useIndexUpdates()`
131130

132131
A minimal query which just returns the current `revision` of the datacore index. The index `revision` is a monotonically increasing number
133132
which is incremented every time something in your vault changes. This call is mainly useful if you are making heavy usage of direct
@@ -136,8 +135,306 @@ changes in your vault.
136135

137136
```jsx
138137
return function View() {
138+
// Revision will update on every index update.
139+
const revision = dc.useIndexUpdates();
140+
141+
// Run some complex query that will be re-run on every revision update.
142+
const complexQuery = dc.useMemo(() => {
143+
const thing = dc.query(/* ... */);
144+
// ...
145+
}, [revision]);
146+
}
147+
```
148+
149+
Like the other hooks, `dc.useIndexUpdates` accepts an optional settings parameter, which allows you to set a debounce:
150+
151+
```jsx
152+
// Only update at most once every ten seconds.
153+
const revision = dc.useIndexUpdates({ debounce: 10000 });
154+
```
155+
156+
## Common React Hooks
157+
158+
Datacore forwards the most common React hooks through it's API to make them available. The full list, with brief explanations of each, is:
159+
160+
- `dc.useState`: Create a React state variable that can be read and updated.
161+
- `dc.useReducer`: Create a React reducer which accepts messages to update internal state.
162+
- `dc.useMemo`: Memoize a value so it only updates when a dependency array changes.
163+
- `dc.useCallback`: Memoize a function so it only is re-created when a dependency array changes.
164+
- `dc.useEffect`: Run a specific 'side-effect' whenever a dependency array changes.
165+
- `dc.createContext`: Create a react context which allows passing state down many layers without prop drilling.
166+
- `dc.useContext`: Use a previously created context.
167+
- `dc.useRef`: A state-like variable that allows directly storing a value without causing React re-renders.
168+
169+
Datacore also provides a few other useful hooks for specifically interacting with datacore utilities:
170+
171+
### `dc.useArray()`
172+
173+
Accepts a regular array, wraps it in a data array, executes a function on the data array, and then converts back to a normal array.
174+
This is primarily useful for when you want to take advantage of [Data Array](data-array) utilities while otherwise using vanilla
175+
javascript arrays for compatibility with preact/react.
176+
177+
```jsx
178+
return function View() {
179+
const pages = dc.useQuery("@page and #book");
180+
const grouped = dc.useArray(pages, array => array.groupBy(book => book.value("genre")));
181+
182+
return <dc.List rows={grouped} renderer={book => book.$link} />
183+
}
184+
```
185+
186+
`dc.useArray` also accepts a dependency array if you depend on state other than the array itself:
187+
188+
```jsx
189+
const [searchTerm, setSearchTerm] = dc.useState("");
190+
const pages = dc.useQuery("@page and #book");
191+
192+
const filteredPages = dc.useArray(
193+
pages,
194+
array => array.filter(book => book.$title.includes(searchTerm)),
195+
[searchTerm]);
196+
```
197+
198+
## Direct Queries
199+
200+
The datacore API also provides several methods for directly querying the index outside of a hook. These can be called from anywhere, but note that,
201+
because they are not hooks, they will _not_ cause your view to update if the query would update. To have your queries re-run every time the
202+
index changes, combine it with `dc.useIndexUpdates`, which will trigger a re-render on every vault change:
139203

204+
```jsx
205+
return function View() {
206+
// Revision will update on every index update.
207+
const revision = dc.useIndexUpdates();
208+
209+
// Run some complex query that will be re-run on every revision update.
210+
const complexQuery = dc.useMemo(() => {
211+
const thing = dc.query(/* ... */);
212+
// ...
213+
}, [revision]);
140214
}
141215
```
142216

143-
Like the other hooks, `dc.useIndexUpdates` accepts an optional second parameter of configuration.
217+
### `dc.query()`
218+
219+
Execute a [query](/data/query) against the datacore index, returning a list of all matched [results](/data/index). Will raise an exception
220+
if the query is malformed.
221+
222+
```jsx
223+
dc.query("@page") => // list of all pages
224+
dc.query("@page and #book and rating > 7") => // all pages tagged book with a rating higher than 7.
225+
```
226+
227+
### `dc.tryQuery()`
228+
229+
Equivalent to `dc.query`, but returns a datacore `Result` instead of raising an exception.
230+
231+
```jsx
232+
dc.tryQuery("@page") => { successful: true, value: [/* list of pages */] }
233+
dc.tryQuery("fakefunction(@page)") => { successful: false, error: "malformed query..." }
234+
```
235+
236+
### `dc.fullquery()`
237+
238+
Equivalent to `dc.query`, but returns several additional pieces of metadata about how long the query took to execute:
239+
240+
```jsx
241+
dc.fullquery("@page") => {
242+
// Parsed query representation.
243+
query: { type: "type", type: "page" },
244+
// Actual results, like you would get from `dc.query`.
245+
results: [/* list of pages */],
246+
// Query runtime in seconds, accurate to the millisecond.
247+
duration: 0.01,
248+
// Index revision the query was executed against.
249+
revision: 317,
250+
}
251+
```
252+
253+
### `dc.tryFullQuery()`
254+
255+
Equivalent to `dc.fullquery`, but returns a datacore `Result` instead of raising an exception on an invalid query.
256+
257+
```jsx
258+
dc.tryFullQuery("@page") => {
259+
successful: true,
260+
value: {
261+
// Parsed query representation.
262+
query: { type: "type", type: "page" },
263+
// Actual results, like you would get from `dc.query`.
264+
results: [/* list of pages */],
265+
// Query runtime in seconds, accurate to the millisecond.
266+
duration: 0.01,
267+
// Index revision the query was executed against.
268+
revision: 317,
269+
}
270+
}
271+
272+
dc.tryFullQuery("malformed(@page)") => {
273+
successful: false,
274+
error: "malformed query ...",
275+
}
276+
```
277+
278+
## Links
279+
280+
Utilities for creating datacore `Link` types and normalizing paths.
281+
282+
### `dc.resolvePath()`
283+
284+
Resolves a local or absolute path to an absolute path, optionally from a given source path.
285+
286+
```jsx
287+
// Can resolve by file name.
288+
dc.resolvePath("Test") = "location/To/Test.md"
289+
// Can resolve from an alternative source path, in case there are multiple `Test` files.
290+
dc.resolvePath("Test", "utils/Index.md") = "utils/Test.md"
291+
// If it cannot find the file, returns the input path unchanged.
292+
dc.resolvePath("noexist") = "noexist"
293+
```
294+
295+
### `dc.fileLink()`
296+
297+
Create a datacore `Link` from a path to a file. The path can be local or absolute (though it is generally
298+
recommended to use absolute paths everywhere to avoid ambigious links). Datacore will render `Link` objects
299+
automatically as Obsidian links, and some APIs may require `Link` objects.
300+
301+
```jsx
302+
dc.fileLink("Test.md") = // Link object representing [[Test]].
303+
```
304+
305+
### `dc.headerLink()`
306+
307+
Create a datacore `Link` pointing to a header in a file.
308+
309+
```jsx
310+
dc.headerLink("Terraria.md", "Review") = // equivalent to [[Terraria#Review]].
311+
```
312+
313+
### `dc.blockLink()`
314+
315+
Create a datacore `Link` pointing to a specific block in a file. Note that blocks can only be linked to if
316+
they have a block ID - generally visible by looking for `^blockId` notation at the end of the block.
317+
318+
```jsx
319+
dc.blockLink("Daily Thoughts.md", "38ha12d") = // equivalent to [[Daily Thoughts#^38ha12d]]
320+
```
321+
322+
### `dc.parseLink()`
323+
324+
Parses a full link into a datacore `Link`. Throws an error if the syntax is malformed.
325+
326+
```jsx
327+
dc.parseLink("[[Test]]") = // link representing [[Test]].
328+
dc.parseLink("[malformed]") = // throws an exception
329+
```
330+
331+
### `dc.tryParseLink()`
332+
333+
Returns a datacore `Result` containing the result of trying to parse a string link.
334+
335+
```jsx
336+
dc.tryParseLink("[[Test]]") = // { successful: true, value: [[Test]] }
337+
dc.tryParseLink("[malformed]") = // { successful: false, error: "malformed input..." }
338+
```
339+
340+
## Expressions
341+
342+
Methods for evaluating arbitrary datacore expressions, and returning their results.
343+
344+
### `dc.evaluate()`
345+
346+
Evaluates a datacore [expression](/expressions), returning what it evaluates to. If the expression cannot be parsed
347+
or is invalid, will raise an exception. `dc.evaluate` accepts one, two, or three arguments:
348+
349+
```jsx
350+
// Single argument version takes only the expression.
351+
dc.evaluate("1 + 2") = 3
352+
353+
// Two argument version allows you to provide variables.
354+
dc.evaluate("x + y", { x: 1, y: 2 }) = 3
355+
356+
// Three argument version allows you to specify a source path to resolve
357+
// links from, if you don't want to use the current file.
358+
dc.evaluate("[[Test]].value", {}, "path/to/other/file.md") = // the value of property 'value' in [[Test]]
359+
```
360+
361+
### `dc.tryEvaluate()`
362+
363+
Equivalent to `dc.evaluate()`, but returns a datacore `Result` type instead of just the value.
364+
365+
```jsx
366+
dc.tryEvaluate("1 + 2") = { value: 3, successful: true }
367+
dc.tryEvaluate("fakefunction(3)") = { successful: false, error: "unrecognized function..." }
368+
```
369+
370+
## Type Coercion / Parsing
371+
372+
Parses
373+
374+
### `dc.coerce.string()`
375+
376+
Converts any other type to a string.
377+
378+
```jsx
379+
dc.coerce.string(16) = "16"
380+
dc.coerce.string(true) = "true"
381+
```
382+
383+
### `dc.coerce.boolean()`
384+
385+
Parses `true` and `false` strings into booleans; returns undefined for most other types.
386+
387+
```jsx
388+
dc.coerce.boolean(true) = true
389+
dc.coerce.boolean("true") = true
390+
dc.coerce.boolean("blah") = undefined
391+
```
392+
393+
### `dc.coerce.number()`
394+
395+
Parses strings into numbers; returns undefined for most other types.
396+
397+
```jsx
398+
dc.coerce.number(15) = 15
399+
dc.coerce.number("49.2") = 49.2
400+
dc.coerce.number("oof") = undefined
401+
```
402+
403+
### `dc.coerce.date()`
404+
405+
Parses strings into dates; returns undefined for most other types.
406+
407+
```jsx
408+
dc.coerce.date("2025-05-10") = // <DateTime representing 2025-05-10>
409+
dc.coerce.date("2025-05-10T11:12:13") = // <DateTime representing 2025-05-10 at 11:12 (and 13 seconds)>
410+
dc.coerce.date("random text") = undefined
411+
```
412+
413+
### `dc.coerce.duration()`
414+
415+
Parses strings into durations; returns undefined for most other types
416+
417+
```jsx
418+
dc.coerce.duration("14 hours") = // <Duration representing 14 hours>
419+
dc.coerce.duration("30m") = // <Duration representing 30 minutes>
420+
dc.coerce.duration("other text") = undefined
421+
```
422+
423+
### `dc.coerce.link()`
424+
425+
Parses strings into links; returns undefined for most other types.
426+
427+
```jsx
428+
dc.coerce.link("[[Test]]") = // Link to 'Test'
429+
dc.coerce.link("![[Embed|Display]]") = // Embedded link to 'Embed' with display 'Display'.
430+
dc.coerce.link("oof") = undefined
431+
```
432+
433+
### `dc.coerce.array()`
434+
435+
If the input is an array, returns that array unchanged; otherwise, wraps the value in an array.
436+
437+
```jsx
438+
dc.coerce.array([1, 2]) = [1, 2]
439+
dc.coerce.array(1) = [1]
440+
```

src/api/api.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,20 @@ export class DatacoreApi {
149149
return DataArray.wrap(input);
150150
}
151151

152-
/** Evaluate an expression and return it's evaluated value. */
152+
/** Evaluate an expression and return it's evaluated value, throwing an exception on failure. */
153153
public evaluate(
154154
expression: string | Expression,
155155
variables?: Record<string, Literal> | any,
156156
sourcePath?: string
157+
): Literal {
158+
return this.tryEvaluate(expression, sourcePath, variables).orElseThrow();
159+
}
160+
161+
/** Evaluate an expression and return it's evaluated value. */
162+
public tryEvaluate(
163+
expression: string | Expression,
164+
variables?: Record<string, Literal> | any,
165+
sourcePath?: string
157166
): Result<Literal, string> {
158167
if (typeof expression === "string") {
159168
const parsed = EXPRESSION.expression.parse(expression);
@@ -164,15 +173,6 @@ export class DatacoreApi {
164173
return this.core.datastore.evaluator(sourcePath).evaluate(expression, Variables.infer(variables));
165174
}
166175

167-
/** Evaluate an expression and return it's evaluated value, throwing an exception on failure. */
168-
public tryEvaluate(
169-
expression: string | Expression,
170-
variables?: Record<string, Literal> | any,
171-
sourcePath?: string
172-
): Literal {
173-
return this.evaluate(expression, sourcePath, variables).orElseThrow();
174-
}
175-
176176
/////////////////////
177177
// Visual Elements //
178178
/////////////////////

0 commit comments

Comments
 (0)