Skip to content

Dependent computations ordering and stale data #652

@legeana

Description

@legeana

Please fill out these Check-boxes

  • I checked for existing similar issues
  • I checked that the plugin is up to date
  • The issue persists with all other plugins and themes disabled

Plugin Version

1.4.6

This Issue Occurs on

  • Windows
  • Linux
  • macOS
  • Android
  • iOS

Debug Info

SYSTEM INFO:
	Obsidian version: 1.12.4
	Installer version: 1.12.4
	Operating system: #1 SMP PREEMPT_DYNAMIC Mon Jan 19 05:47:43 UTC 2026 6.17.7-ba25.fc43.x86_64
	Login status: logged in
	Language: en-GB
	Catalyst license: none
	Insider build toggle: off
	Live preview: on
	Base theme: dark
	Community theme: none
	Snippets enabled: 4
	Restricted mode: off
	Plugins installed: 20
	Plugins enabled: 9
		1: CSS Editor v1.13.2
		2: Dataview v0.5.68
		3: Meta Bind v1.4.6
		4: Paste URL into selection v1.11.4
		5: Plugin Update Tracker v1.7.0
		6: Tasks v7.23.1
		7: TODO | Text-based GTD v0.2.8
		8: Version History Diff v2.3.3
		9: JS Engine v0.3.3

RECOMMENDATIONS:
	Custom theme and snippets: for cosmetic issues, please first try updating your theme and disabling your snippets. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community theme and snippets.
	Community plugins: for bugs, please first try updating all your plugins to latest. If still not fixed, please try to make the issue happen in the Sandbox Vault or disable community plugins.

Describe the Issue

Say we have a series of dependent computations:

  • b = a + 1
  • c = b + 1
  • d = c + 1
  • e = d + 1

And say we configure them to be in their own separate views like so:

{a} as a
save to {memory^b}
---
console.log(`Computing memory^b from ${context.bound.a}`);
return context.bound.a + 1;
{memory^b} as b
save to {memory^c}
---
console.log(`Computing memory^c from ${context.bound.b}`);
return context.bound.b + 1;

etc

Then it takes 4 round to compute everything:

// Round 1.
Computing b from 1
Computing c from undefined
Computing d from undefined
Computing e from undefined
// Round 2.
Computing c from 2
Computing d from NaN
Computing e from NaN
// Round 3.
Computing d from 3
Computing e from NaN
// Round 4.
Computing e from 4

The issue is, each round everything is computed from the same snapshot, and a lot of data is not yet available. For a simple case like here we a just getting NaNs. However if we compute objects then we get a lot of noisy errors accessing fields of undefined, e.g.

{memory^c} as c
save to {memory^d}
---
console.log(`Computing memory^d from ${context.bound.c}`);
return {value: context.bound.c.value + 1};

Steps to Reproduce

  1. Create this note: Meta Bind Test.md. Or this simpler version Meta Bind Test 2.md with no undefined dereferences.
  2. Close Obsidian.
  3. Open Obsidian.
  4. Open the new note with rendering on. The goal is to trigger evaluation with no previous state.
  5. Observe errors in the UI: Image
  6. Observe errors in the Console log: Image
app.js:1 Obsidian Developer Console
plugin:dataview:20478 Dataview: version 0.5.68 (requires obsidian 0.13.11)
plugin:obsidian-meta-bind-plugin:184 meta-bind | Main >> loading
plugin:obsidian-meta-bind-plugin:184 meta-bind | Main >> loading settings
plugin:obsidian-meta-bind-plugin:184 meta-bind | Main >> load-time: 12.26513671875 ms
plugin:url-into-selection:8 loading url-into-selection
plugin:obsidian-tasks-plugin:188 Language in Obsidian settings: 'en'; requesting Tasks in 'en'.
plugin:obsidian-tasks-plugin:236 [2026-03-12-23:44:35.927][info][tasks] Loading plugin: Tasks v7.23.1 
plugin:obsidian-version-history-diff:452 loading Version History Diff plugin
plugin:js-engine:70 Initializing MessageManager status bar item
initStatusBarItem @ plugin:js-engine:70
onload @ plugin:js-engine:126
plugin:dataview:13006 Dataview: all 273 files have been indexed in 0.058s (273 cached, 0 skipped).
plugin:obsidian-plugin-todo:9801 [obsidian-plugin-todo] Parsed 133 TODOs from 273 markdown files in (0.173s)
plugin:js-engine:JS_EXECUTION:f079657d-4086-4c91-be87-252cfe292e2e:4 Computing memory^b from 1
plugin:js-engine:JS_EXECUTION:dad251f4-980c-4651-9594-ab7bfd9b0231:4 Computing memory^c from undefined
plugin:js-engine:JS_EXECUTION:1702466b-6eb1-4ce9-93cf-4c2ca5bf1f4d:4 Computing memory^d from undefined
plugin:js-engine:JS_EXECUTION:e900bb40-60c3-4b93-b4a0-c4901651b8d6:4 Computing memory^e from undefined
plugin:js-engine:75 failed to execute JS TypeError: Cannot read properties of undefined (reading 'value')
    at ja.anonymous [as func] (plugin:js-engine:JS_EXECUTION:dad251f4-980c-4651-9594-ab7bfd9b0231:5:32)
    at ja.runFunction (plugin:js-engine:75:636)
    at Ss.execute (plugin:js-engine:126:76724)
    at ns.execute (plugin:js-engine:115:1753)
    at nx.evaluateCode (plugin:obsidian-meta-bind-plugin:176:10531)
    at async nx.evaluate (plugin:obsidian-meta-bind-plugin:176:10839)
runFunction @ plugin:js-engine:75
plugin:js-engine:75 failed to execute JS TypeError: Cannot read properties of undefined (reading 'value')
    at ja.anonymous [as func] (plugin:js-engine:JS_EXECUTION:1702466b-6eb1-4ce9-93cf-4c2ca5bf1f4d:5:32)
    at ja.runFunction (plugin:js-engine:75:636)
    at Ss.execute (plugin:js-engine:126:76724)
    at ns.execute (plugin:js-engine:115:1753)
    at nx.evaluateCode (plugin:obsidian-meta-bind-plugin:176:10531)
    at async nx.evaluate (plugin:obsidian-meta-bind-plugin:176:10839)
runFunction @ plugin:js-engine:75
plugin:js-engine:75 failed to execute JS TypeError: Cannot read properties of undefined (reading 'value')
    at ja.anonymous [as func] (plugin:js-engine:JS_EXECUTION:e900bb40-60c3-4b93-b4a0-c4901651b8d6:5:31)
    at ja.runFunction (plugin:js-engine:75:636)
    at Ss.execute (plugin:js-engine:126:76724)
    at ns.execute (plugin:js-engine:115:1753)
    at nx.evaluateCode (plugin:obsidian-meta-bind-plugin:176:10531)
    at async nx.evaluate (plugin:obsidian-meta-bind-plugin:176:10839)
runFunction @ plugin:js-engine:75
plugin:js-engine:70 Object
plugin:js-engine:70 Object
plugin:js-engine:70 Object
plugin:js-engine:JS_EXECUTION:6451983e-b55f-409a-b467-6ea9f2c9dcc9:4 Computing memory^c from [object Object]
plugin:js-engine:JS_EXECUTION:1bc2f5c2-4165-43d0-a9c8-723bde73cdf0:4 Computing memory^d from [object Object]
plugin:js-engine:JS_EXECUTION:a3d55d0d-eb3f-4a7f-ac56-5b9abb845955:4 Computing memory^e from [object Object]
plugin:js-engine:JS_EXECUTION:b78785f9-4940-4285-afc9-f43f504b3a5c:4 Computing memory^d from [object Object]
plugin:js-engine:JS_EXECUTION:4e0e4507-ffd7-44d9-8625-e05f1afb5aa0:4 Computing memory^e from [object Object]
plugin:js-engine:JS_EXECUTION:3a264a1f-598e-4aca-bb1c-6dcd76dd0ad9:4 Computing memory^e from [object Object]
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}
plugin:js-engine:70 {executionSource: 'markdown-other', file: t, metadata: {…}}

Expected Behavior

Expected behaviour

I expected this to succeed evaluating without errors because the views are executed in the correct order. But the data is not available for some reason.

I don't know whether to focus on stale data or the evaluation order, so I proposed a couple of ideas.

Proposal: skip evaluations of views with undefined input data

If there are compatibility concerns then consider making this behaviour guarded by an option (e.g. skip-if-undefined-inputs).

This will look like this, for the original example:

  • Round 1: Compute b, skip c, skip d, skip e.
  • Round 2: Compute c, skip d, skip e.
  • Round 3: Compute d, skip e.
  • Round 4: Compute e.

Round 2 happens because b was updated and c depends on it, and so on.

Alternative: make the user topologically views for the plugin

  • Specify that views are evaluated in order they were defined on the page.
  • Update the storage on every view evaluation, not at the end of the round.

This issue can be entirely offloaded to the user, and if their evaluations error out, they can just rearrange their views. Or put hidden views at the top to handle complex dependent evaluations before simple rendering evaluations.

Alternative: reorder computations within the round, O(N^2)

This implementation has a somewhat questionable complexity, but it doesn't require predicting what data each evaluation will emit. Plus, the current implementation is already O(N^2) if there are dependent computations anyway, but it unlike this proposal the current implementation wastes more time attempting computations that are likely going to fail.

This can be implemented for example like so, for every round:

  • Make two queues: q_this, q_next.
  • Put all evaluations into q_this.
  • While elements left in either q_this or q_next:
    • If q_this is empty, swap with q_next.
    • Pop from q_this into popped:
      • If popped has all of its input data available, evaluate and update the storage.
      • The popped has any of its input data unavailable:
        • If q_this is empty, evaluate anyway. All other computations failed to produce the necessary data, so we can't do better. This checks guarantees we remove at least one element between queue swaps and prevents infinite loops.
        • If q_this is not empty, push popped into q_next. Maybe another evaluation from q_this will compute the dependent input.

This implementation is more complex than my main proposal and will lead to similar results in most cases. However it will always evaluate all views on every round, which may be more desirable.

Alternative: topologically sort the views

This works well only with views that define save to. It is harder if not impossible to implement for views that use setMetadata.

  • Build a graph of view dependencies.
  • Topologically sort the graph.
  • Evaluate in order.

User workarounds currently available

  • if (context.bound.every_input === undefined) return;, preferred.
  • Using frontmatter instead of memory storage will result in a number of rounds with stale data, but will prevent undefined dereference errors. The data would eventually stabilise and the next load requires less rounds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugUndesired behavior caused by this plugin

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions