Skip to content

ResourceLockComponent

A per-plugin handle for path-scoped resource locking (editor read-only, optional vault-mutation blocking, and external-change detection — see the file overview). Add it as a child of your plugin (this.addChild(new ResourceLockComponent(this.app))) so that any locks it still holds are released automatically when the plugin unloads — a resource can never be left stuck locked because the plugin that locked it was disabled or reloaded mid-operation.

Locks are reference-counted and attributed to this plugin, so the lock indicators’ tooltip names it among the plugins currently holding a lock.

Import:

import { ResourceLockComponent } from 'obsidian-dev-utils/obsidian/resource-lock';

Signature:

export class ResourceLockComponent extends ComponentEx

Extends: ComponentEx

Constructor

new ResourceLockComponent(app: App, pluginId: string)

Creates an resource-lock handle owned by a plugin.

Properties

PropertyTypeDescription
appAppThe Obsidian app instance.
pluginIdstringThe id of the owning plugin (e.g. its manifest.id). Locks are attributed to it for reference-counting and the indicators' "locked by" tooltip.

Methods

MethodReturnsDescription
[Symbol.dispose]()voidDisposes of the component.
(Inherited from ComponentEx)
addChild(component)TComponentAdds a child component.

Mirrors the native Component.addChild contract: if this component is already loaded, the child is loaded immediately, so child._loaded is set before this method returns even when this component has async load logic. The child's async tail (if any) is sequenced into the load promise so a later loadWithPromises call awaits it.
(Inherited from ComponentEx)
bypassBlockedMutations(pathsOrFiles)DisposableOpens an **ambient bypass scope**: while the returned Disposable is not disposed, this plugin's own mutations of the given paths (and, for a folder, its whole subtree) pass through the mutation blocker instead of throwing ResourceLockedError. Any mutation of a blocked path NOT covered by an active bypass scope is still rejected. Use it with a using declaration around the operation that legitimately mutates resources it has locked with ResourceLockComponentLockForPathParams.shouldBlockMutations:

``ts
using _bypass = component.bypassBlockedMutations([sourceFile, targetFile]);
// ... the operation's own vault mutations of those paths are allowed ...
// scope end: the paths are enforced again
``
ensureLoaded()voidEnsures the component is loaded, throwing if it is not.

Use this to guard public methods that register teardown-bearing resources (via Component.register, Component.registerEvent, Component.registerDomEvent, etc.). Registering before load is unsafe: Component.unload is a no-op while the component is not loaded, so any teardown registered beforehand would never run if the component is unloaded without first being loaded.
(Inherited from ComponentEx)
isLockedByAncestorForPath(pathOrFile)booleanChecks whether the path is locked directly or lies under a subtree-locked ancestor folder.
isLockedForPath(pathOrFile)booleanChecks whether the note at the given path is currently locked by any plugin.
isMutationBlockedByAncestorForPath(pathOrFile)booleanChecks whether a vault mutation of the path is currently blocked by a shouldBlockMutations lock on the path itself or on a subtree-locked ancestor folder.
load()voidLoads the component.
(Inherited from ComponentEx)
loadWithPromises()null | Promise<void>Loads the component with promises.

Unlike load, this method never rejects with an individual error: every failure raised by onloadAsync or a child's load is collected, and once everything settles the returned Promise rejects with a single AggregateError holding all of them. Non-Error throwables are normalized via ErrorWrapper.create.
(Inherited from ComponentEx)
lockForPath(params)DisposableLocks the note at the given path on behalf of this plugin, making it read-only in every current and future MarkdownView until the lock is released. Reference-counted: balance each call with a dispose of the returned Disposable (ideally via using) or unlockForPath.
onloadAsync()Promise<void>Asynchronously loads the component.

Override to add async load logic, which is executed after Component.onload.
(Inherited from ComponentEx)
onunload()voidReleases every lock still held by this plugin when the component (and thus the plugin) unloads, so no note is left stuck read-only by an operation that never completed.
registerDisposable(disposable)TDisposableRegisters a Disposable so it is disposed when this component unloads, and returns it unchanged.

The recurring "tie a disposable to the component's lifecycle, then keep using it" idiom: the disposable is disposed on Component.unload (or earlier, if the caller disposes it directly — dispose is expected to be idempotent). Guard with ensureLoaded at the call site when registering before load would be unsafe.
(Inherited from ComponentEx)
removeChild(component)TComponentRemoves a child component.
(Inherited from ComponentEx)
requestUnlockForPath(pathOrFile)voidFully unlocks the note at the given path: resolves the lock covering it (the path itself when directly locked, otherwise a subtree-locked ancestor folder), cancels the operation(s) that took the lock by aborting their AbortControllers, and releases the lock so the note becomes editable — regardless of which plugin holds the lock. Powers the "Unlock active note" command. A no-op when nothing covers the path.
unlockForPath(pathOrFile)voidReleases one lock previously acquired for the note at the given path via lockForPath.

Links to this page: