@lapis-notes/api 0.1.4 → 0.2.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.
Files changed (188) hide show
  1. package/CHANGELOG.md +206 -0
  2. package/README.md +21 -2
  3. package/dist/__tests__/fixtures/indexed-mutations.json +6862 -0
  4. package/dist/__tests__/fixtures/memory-storage.json +309 -0
  5. package/dist/__tests__/fixtures/native-facade-contract.json +3224 -0
  6. package/dist/__tests__/fixtures/projection-query.json +195 -0
  7. package/dist/__tests__/fixtures/projection-values.json +60 -0
  8. package/dist/__tests__/fixtures/search-preparation.json +1513 -0
  9. package/dist/__tests__/fixtures/search-retrieval.json +15409 -0
  10. package/dist/__tests__/fixtures/search-semantic-model.json +3691 -0
  11. package/dist/__tests__/fixtures/search-semantic-retrieval.json +6603 -0
  12. package/dist/__tests__/fixtures/search-storage.json +409 -0
  13. package/dist/__tests__/fixtures/task-projection.json +428 -0
  14. package/dist/__tests__/indexed-mutation-support.js +262 -0
  15. package/dist/__tests__/projection-query-support.js +40 -0
  16. package/dist/__tests__/search-retrieval-support.js +47 -0
  17. package/dist/app-database-host.d.ts +1 -0
  18. package/dist/app-shell-plugin-persistence.d.ts +3 -2
  19. package/dist/app-shell-plugin-persistence.js +37 -3
  20. package/dist/cache.svelte.d.ts +8 -1
  21. package/dist/cache.svelte.js +103 -5
  22. package/dist/command.svelte.js +29 -2
  23. package/dist/components/editor/editor.css +4 -0
  24. package/dist/components/editor/editor.svelte +21 -9
  25. package/dist/components/editor/embedded-editor-surface.js +3 -1
  26. package/dist/components/editor/inline-title-alignment.d.ts +3 -0
  27. package/dist/components/editor/inline-title-alignment.js +57 -0
  28. package/dist/configuration.svelte.d.ts +270 -206
  29. package/dist/configuration.svelte.js +161 -86
  30. package/dist/context.svelte.d.ts +21 -3
  31. package/dist/context.svelte.js +63 -20
  32. package/dist/diagnostics.d.ts +8 -0
  33. package/dist/diagnostics.js +6 -0
  34. package/dist/editor-save-context.d.ts +12 -1
  35. package/dist/editor-save-context.js +32 -1
  36. package/dist/editor-view-registry.d.ts +10 -0
  37. package/dist/editor-view-registry.js +44 -4
  38. package/dist/editor.svelte.js +128 -37
  39. package/dist/embed-registry.d.ts +10 -0
  40. package/dist/embed-registry.js +15 -0
  41. package/dist/file-editing-failures.d.ts +2 -0
  42. package/dist/file-editing-failures.js +29 -0
  43. package/dist/file-editing-native-issues.d.ts +19 -0
  44. package/dist/file-editing-native-issues.js +65 -0
  45. package/dist/file-editing-sessions.d.ts +56 -8
  46. package/dist/file-editing-sessions.js +185 -15
  47. package/dist/file-manager.d.ts +8 -1
  48. package/dist/file-manager.js +37 -3
  49. package/dist/file-state-store.d.ts +5 -0
  50. package/dist/file-state-store.js +34 -0
  51. package/dist/file-text-conflicts.d.ts +25 -0
  52. package/dist/file-text-conflicts.js +35 -0
  53. package/dist/file-text-mutation.d.ts +13 -0
  54. package/dist/file-text-mutation.js +100 -0
  55. package/dist/file-text-persistence.d.ts +36 -0
  56. package/dist/file-text-persistence.js +234 -0
  57. package/dist/file-text-state-writer.d.ts +34 -0
  58. package/dist/file-text-state-writer.js +183 -0
  59. package/dist/files-configuration.d.ts +20 -0
  60. package/dist/files-configuration.js +47 -0
  61. package/dist/generated/plugin-host-modules.schema.generated.d.ts +87 -37
  62. package/dist/generated/plugin-host-modules.schema.generated.js +102 -37
  63. package/dist/glob.d.ts +3 -0
  64. package/dist/glob.js +2 -1
  65. package/dist/icons/changes.d.ts +3 -0
  66. package/dist/icons/changes.js +16 -0
  67. package/dist/icons/index.d.ts +5 -1
  68. package/dist/icons/index.js +44 -3
  69. package/dist/icons/registry.d.ts +35 -0
  70. package/dist/icons/registry.js +154 -0
  71. package/dist/icons/safe-svg.d.ts +2 -0
  72. package/dist/icons/safe-svg.js +44 -0
  73. package/dist/index.d.ts +5 -1
  74. package/dist/index.js +3 -1
  75. package/dist/lapis-extension.d.ts +6 -2
  76. package/dist/lapis-extension.js +2 -0
  77. package/dist/metadata.svelte.js +45 -2
  78. package/dist/native-background-indexing.d.ts +48 -0
  79. package/dist/native-background-indexing.js +208 -0
  80. package/dist/native-database-protocol.d.ts +29 -0
  81. package/dist/native-database-protocol.js +1 -0
  82. package/dist/native-editing-intent-lease.d.ts +17 -0
  83. package/dist/native-editing-intent-lease.js +188 -0
  84. package/dist/native-editing-intents-protocol.d.ts +70 -0
  85. package/dist/native-editing-intents-protocol.js +1 -0
  86. package/dist/native-editing-intents.d.ts +8 -0
  87. package/dist/native-editing-intents.js +1 -0
  88. package/dist/native-editing-problems.d.ts +3 -0
  89. package/dist/native-editing-problems.js +180 -0
  90. package/dist/native-editing-recovery.d.ts +24 -0
  91. package/dist/native-editing-recovery.js +198 -0
  92. package/dist/native-editing-session.d.ts +51 -0
  93. package/dist/native-editing-session.js +251 -0
  94. package/dist/native-history-index-protocol.d.ts +80 -0
  95. package/dist/native-history-index-protocol.js +1 -0
  96. package/dist/native-history-index.d.ts +6 -0
  97. package/dist/native-history-index.js +1 -0
  98. package/dist/native-indexing-protocol.d.ts +63 -0
  99. package/dist/native-indexing-protocol.js +1 -0
  100. package/dist/native-participant-protocol.d.ts +67 -0
  101. package/dist/native-participant-protocol.js +3 -0
  102. package/dist/native-queue-protocol.d.ts +73 -0
  103. package/dist/native-queue-protocol.js +3 -0
  104. package/dist/native-tasks-protocol.d.ts +264 -0
  105. package/dist/native-tasks-protocol.js +1 -0
  106. package/dist/native-tasks.d.ts +35 -0
  107. package/dist/native-tasks.js +79 -0
  108. package/dist/native-turn-protocol.d.ts +81 -0
  109. package/dist/native-turn-protocol.js +1 -0
  110. package/dist/native-turns.d.ts +29 -0
  111. package/dist/native-turns.js +2 -0
  112. package/dist/plugin-asset-server.js +3 -2
  113. package/dist/plugin-dependency-resolver.d.ts +1 -1
  114. package/dist/plugin-distribution/compatibility.d.ts +1 -1
  115. package/dist/plugin-distribution/compatibility.js +10 -5
  116. package/dist/plugin-distribution/installed-plugin-state.js +2 -1
  117. package/dist/plugin-distribution/installer.d.ts +6 -0
  118. package/dist/plugin-distribution/installer.js +90 -37
  119. package/dist/plugin-distribution/manager.d.ts +4 -1
  120. package/dist/plugin-distribution/manager.js +12 -5
  121. package/dist/plugin-distribution/registry-cache.js +2 -1
  122. package/dist/plugin-distribution/registry-source-state.js +2 -1
  123. package/dist/plugin-distribution/schemas.d.ts +26 -8
  124. package/dist/plugin-distribution/schemas.js +2 -1
  125. package/dist/plugin-distribution/types.d.ts +4 -0
  126. package/dist/plugin-manager.d.ts +20 -4
  127. package/dist/plugin-manager.js +228 -64
  128. package/dist/plugin-runtime-entry.d.ts +1 -1
  129. package/dist/plugin-runtime-entry.js +16 -0
  130. package/dist/plugin.d.ts +18 -2
  131. package/dist/plugin.js +93 -39
  132. package/dist/registry-schema/catalog-v1.d.ts +60 -9
  133. package/dist/registry-schema/catalog-v1.js +2 -1
  134. package/dist/registry-schema/catalog-v2.d.ts +60 -9
  135. package/dist/registry-schema/catalog-v2.js +1 -1
  136. package/dist/search-result-provider.d.ts +95 -0
  137. package/dist/search-result-provider.js +42 -0
  138. package/dist/status-bar.svelte.d.ts +26 -20
  139. package/dist/status-bar.svelte.js +1 -0
  140. package/dist/storage/app-database.d.ts +6 -2
  141. package/dist/storage/app-database.js +41 -34
  142. package/dist/storage/desktop-native.d.ts +5 -3
  143. package/dist/storage/desktop-native.js +59 -6
  144. package/dist/storage/directory-watcher.d.ts +27 -0
  145. package/dist/storage/directory-watcher.js +201 -0
  146. package/dist/storage/fs.d.ts +14 -27
  147. package/dist/storage/fs.js +2 -168
  148. package/dist/storage/index-projection.js +33 -15
  149. package/dist/storage/memory-vault-adapter.d.ts +2 -0
  150. package/dist/storage/memory-vault-adapter.js +14 -1
  151. package/dist/storage/path.d.ts +6 -2
  152. package/dist/storage/path.js +8 -2
  153. package/dist/storage/projection-query-candidates.d.ts +3 -0
  154. package/dist/storage/projection-query-candidates.js +69 -0
  155. package/dist/storage/remote-app-database.d.ts +2 -2
  156. package/dist/storage/remote-app-database.js +1 -1
  157. package/dist/storage/runtime.d.ts +3 -0
  158. package/dist/storage/runtime.js +3 -0
  159. package/dist/storage/search-embedding-record.d.ts +3 -0
  160. package/dist/storage/search-embedding-record.js +18 -0
  161. package/dist/storage/turso-app-database.d.ts +2 -0
  162. package/dist/storage/turso-app-database.js +67 -30
  163. package/dist/storage/vault-listing.d.ts +3 -0
  164. package/dist/storage/vault-listing.js +36 -0
  165. package/dist/storage/vault-reconciliation.d.ts +19 -0
  166. package/dist/storage/vault-reconciliation.js +137 -0
  167. package/dist/storage/vault-session.d.ts +8 -0
  168. package/dist/storage/vault-session.js +10 -2
  169. package/dist/storage/vault.svelte.d.ts +53 -4
  170. package/dist/storage/vault.svelte.js +223 -87
  171. package/dist/sync-host.d.ts +12 -3
  172. package/dist/sync-host.js +5 -1
  173. package/dist/vault-change-views.d.ts +4 -0
  174. package/dist/vault-change-views.js +43 -0
  175. package/dist/vault-changes.d.ts +37 -0
  176. package/dist/vault-changes.js +228 -0
  177. package/dist/view.svelte.d.ts +182 -149
  178. package/dist/view.svelte.js +11 -2
  179. package/dist/workspace-close-guard.d.ts +3 -0
  180. package/dist/workspace-close-guard.js +35 -0
  181. package/dist/workspace-host-internal.js +10 -2
  182. package/dist/workspace-layout-normalizer.d.ts +4 -2
  183. package/dist/workspace-layout-normalizer.js +5 -5
  184. package/dist/workspace-view-document.d.ts +3 -0
  185. package/dist/workspace-view-document.js +12 -0
  186. package/dist/workspace.svelte.d.ts +13 -2
  187. package/dist/workspace.svelte.js +371 -82
  188. package/package.json +45 -8
@@ -1,15 +1,20 @@
1
- export const checkCatalogEntryCompatibility = (entry, input) => checkBaseCompatibility(entry.minAppVersion, entry.platforms, false, false, false, input);
2
- export const checkCatalogReleaseCompatibility = (release, input) => checkBaseCompatibility(release.minAppVersion, release.platforms, false, false, Boolean(release.revoked), input);
3
- export const checkReleaseManifestCompatibility = (manifest, input) => checkBaseCompatibility(manifest.compatibility.minAppVersion, manifest.compatibility.platforms, Boolean(manifest.compatibility.desktopOnly), Boolean(manifest.compatibility.requiresWorkspaceTrust), Boolean(manifest.revoked), input);
4
- const checkBaseCompatibility = (minAppVersion, platforms, desktopOnly, requiresWorkspaceTrust, revoked, input) => {
1
+ export const checkCatalogEntryCompatibility = (entry, input) => checkBaseCompatibility(entry.id, entry.minAppVersion, entry.platforms, false, false, false, input);
2
+ export const checkCatalogReleaseCompatibility = (release, input, pluginId = "") => checkBaseCompatibility(pluginId, release.minAppVersion, release.platforms, false, false, Boolean(release.revoked), input);
3
+ export const checkReleaseManifestCompatibility = (manifest, input) => checkBaseCompatibility(manifest.pluginId, manifest.compatibility.minAppVersion, manifest.compatibility.platforms, Boolean(manifest.compatibility.desktopOnly), Boolean(manifest.compatibility.requiresWorkspaceTrust), Boolean(manifest.revoked), input);
4
+ const checkBaseCompatibility = (pluginId, minAppVersion, platforms, desktopOnly, requiresWorkspaceTrust, revoked, input) => {
5
5
  const reasons = [];
6
+ if (Object.hasOwn(input.unavailablePlugins ?? {}, pluginId)) {
7
+ reasons.push(input.unavailablePlugins[pluginId]);
8
+ }
6
9
  if (compareVersions(input.appVersion, minAppVersion) < 0) {
7
10
  reasons.push("app-version-too-old");
8
11
  }
9
12
  if (!platforms.includes(input.platform)) {
10
13
  reasons.push("platform-unsupported");
11
14
  }
12
- if (desktopOnly && input.platform === "web") {
15
+ if (desktopOnly &&
16
+ input.platform === "web" &&
17
+ !reasons.includes("desktop-only")) {
13
18
  reasons.push("desktop-only");
14
19
  }
15
20
  if (requiresWorkspaceTrust && !input.workspaceTrusted) {
@@ -1,3 +1,4 @@
1
+ import { appStatePath } from "../storage/path";
1
2
  import { dirname } from "../storage/path";
2
3
  import { installedPluginsStateSchema, parsePluginDistributionMetadata, } from "./schemas";
3
4
  export class InstalledPluginStateStore {
@@ -57,7 +58,7 @@ export class InstalledPluginStateStore {
57
58
  };
58
59
  }
59
60
  get path() {
60
- return this.options.path ?? ".obsidian/installed-plugins.json";
61
+ return this.options.path ?? appStatePath("installed-plugins.json");
61
62
  }
62
63
  now() {
63
64
  return (this.options.now?.() ?? new Date()).toISOString();
@@ -19,6 +19,12 @@ export interface PluginInstallerProgressEvent {
19
19
  fileCount?: number;
20
20
  }
21
21
  export interface PluginInstallerManager {
22
+ unloadPlugin?(pluginId: string): Promise<boolean>;
23
+ isStaticPlugin?(pluginId: string): boolean;
24
+ preparePluginReplacement?(pluginId: string): Promise<{
25
+ reloadRequired: boolean;
26
+ rollback(): Promise<void>;
27
+ } | undefined>;
22
28
  loadPlugin?(pluginPath: string, options?: {
23
29
  provenance?: PluginProvenance;
24
30
  }): Promise<unknown>;
@@ -1,3 +1,4 @@
1
+ import { appStatePath } from "../storage/path";
1
2
  import { basename, dirname, joinPath } from "../storage/path";
2
3
  import { checkReleaseManifestCompatibility } from "./compatibility";
3
4
  import { PluginDistributionError } from "./errors";
@@ -64,7 +65,15 @@ export class VerifiedPluginInstaller {
64
65
  });
65
66
  }
66
67
  async uninstall(pluginId, options = {}) {
67
- await this.options.pluginManager?.disablePlugin?.(pluginId);
68
+ const manager = this.options.pluginManager;
69
+ if (!manager?.isStaticPlugin?.(pluginId)) {
70
+ if (manager?.unloadPlugin) {
71
+ if (!(await manager.unloadPlugin(pluginId)))
72
+ throw new Error(`Plugin ${pluginId} cleanup failed; uninstall was cancelled.`);
73
+ }
74
+ else
75
+ await manager?.disablePlugin?.(pluginId);
76
+ }
68
77
  const pluginPath = joinPath(this.pluginsPath, pluginId);
69
78
  if (options.removeData) {
70
79
  await this.removeIfExists(pluginPath);
@@ -140,6 +149,9 @@ export class VerifiedPluginInstaller {
140
149
  }
141
150
  async installVerifiedBundlePayload(payload, options) {
142
151
  const { releaseEnvelope, releaseManifest, releaseManifestSha256, files, nostrProof, } = payload;
152
+ if (this.options.pluginManager?.isStaticPlugin?.(releaseManifest.pluginId)) {
153
+ throw new PluginDistributionError("reserved-id", `Plugin ${releaseManifest.pluginId} is registered by the application.`);
154
+ }
143
155
  if (options.compatibility) {
144
156
  const compatibility = checkReleaseManifestCompatibility(releaseManifest, options.compatibility);
145
157
  if (!compatibility.compatible) {
@@ -170,36 +182,12 @@ export class VerifiedPluginInstaller {
170
182
  }
171
183
  const stagingPath = joinPath(this.pluginsPath, ".installing", `${releaseManifest.pluginId}-${Date.now()}`);
172
184
  const finalPath = joinPath(this.pluginsPath, releaseManifest.pluginId);
173
- try {
174
- throwIfAborted(options.signal);
175
- options.onProgress?.({
176
- phase: "staging",
177
- message: "Staging plugin files",
178
- processedBytes: 0,
179
- totalBytes: totalFileBytes(files),
180
- });
181
- await this.writeStagingFiles(stagingPath, files, {
182
- signal: options.signal,
183
- onProgress: options.onProgress,
184
- });
185
- options.onProgress?.({
186
- phase: "installing",
187
- message: "Installing plugin files",
188
- });
189
- await this.swapIntoPlace(stagingPath, finalPath);
190
- }
191
- catch (error) {
192
- await this.removeIfExists(stagingPath);
193
- if (!(await this.options.adapter.exists(finalPath))) {
194
- await this.removeIfExists(finalPath);
195
- }
196
- throw error;
197
- }
185
+ const previousRecord = await this.stateStore.get(releaseManifest.pluginId);
198
186
  const now = this.now();
199
187
  const record = {
200
188
  pluginId: releaseManifest.pluginId,
201
189
  installedVersion: releaseManifest.version,
202
- installedAt: now,
190
+ installedAt: previousRecord?.installedAt ?? now,
203
191
  updatedAt: now,
204
192
  provenance,
205
193
  registryId: options.registryId,
@@ -219,22 +207,70 @@ export class VerifiedPluginInstaller {
219
207
  ...(nostrProof ? { nostrProof } : {}),
220
208
  restartRequired: Boolean(this.options.pluginManager),
221
209
  };
222
- await this.stateStore.upsert(record);
210
+ let replacement;
211
+ try {
212
+ throwIfAborted(options.signal);
213
+ options.onProgress?.({
214
+ phase: "staging",
215
+ message: "Staging plugin files",
216
+ processedBytes: 0,
217
+ totalBytes: totalFileBytes(files),
218
+ });
219
+ await this.writeStagingFiles(stagingPath, files, {
220
+ signal: options.signal,
221
+ onProgress: options.onProgress,
222
+ });
223
+ if (await this.options.adapter.exists(finalPath)) {
224
+ replacement =
225
+ await this.options.pluginManager?.preparePluginReplacement?.(releaseManifest.pluginId);
226
+ // Read after teardown so its final save wins over release defaults.
227
+ const dataPath = joinPath(finalPath, "data.json");
228
+ if (await this.options.adapter.exists(dataPath)) {
229
+ await this.options.adapter.writeBinary(joinPath(stagingPath, "data.json"), await this.options.adapter.readBinary(dataPath));
230
+ }
231
+ }
232
+ options.onProgress?.({
233
+ phase: "installing",
234
+ message: "Installing plugin files",
235
+ });
236
+ throwIfAborted(options.signal);
237
+ await this.swapIntoPlace(stagingPath, finalPath, {
238
+ publish: () => this.stateStore.upsert(record),
239
+ rollback: async () => {
240
+ if (previousRecord)
241
+ await this.stateStore.upsert(previousRecord);
242
+ else
243
+ await this.stateStore.remove(releaseManifest.pluginId);
244
+ },
245
+ });
246
+ }
247
+ catch (error) {
248
+ try {
249
+ await this.removeIfExists(stagingPath);
250
+ }
251
+ finally {
252
+ await replacement?.rollback();
253
+ }
254
+ throw error;
255
+ }
223
256
  if (this.options.pluginManager?.loadPlugin) {
224
257
  options.onProgress?.({
225
258
  phase: "loading",
226
259
  message: "Loading installed plugin",
227
260
  });
228
261
  }
229
- const loaded = await this.options.pluginManager?.loadPlugin?.(finalPath, {
230
- provenance,
231
- });
262
+ const loaded = replacement?.reloadRequired
263
+ ? null
264
+ : await this.options.pluginManager?.loadPlugin?.(finalPath, {
265
+ provenance,
266
+ });
232
267
  const shouldEnable = options.enable ?? true;
233
268
  const diagnostics = this.options.pluginManager?.getCommunityPluginDiagnostics?.(releaseManifest.pluginId) ?? null;
234
269
  const cleanDormantExtension = diagnostics?.state === "dormant" &&
235
270
  (diagnostics.lastFailureMessage ?? null) === null;
236
271
  let enabled = false;
237
272
  if (shouldEnable &&
273
+ !replacement?.reloadRequired &&
238
274
  (loaded || cleanDormantExtension) &&
239
275
  this.options.pluginManager?.enablePlugin) {
240
276
  options.onProgress?.({
@@ -243,7 +279,8 @@ export class VerifiedPluginInstaller {
243
279
  });
244
280
  enabled = await this.options.pluginManager.enablePlugin(releaseManifest.pluginId);
245
281
  }
246
- record.restartRequired = Boolean(this.options.pluginManager && !loaded && !enabled);
282
+ record.restartRequired = Boolean(this.options.pluginManager &&
283
+ (replacement?.reloadRequired || (!loaded && !enabled)));
247
284
  await this.stateStore.upsert(record);
248
285
  return record;
249
286
  }
@@ -332,24 +369,40 @@ export class VerifiedPluginInstaller {
332
369
  });
333
370
  }
334
371
  }
335
- async swapIntoPlace(stagingPath, finalPath) {
372
+ async swapIntoPlace(stagingPath, finalPath, metadata) {
336
373
  await ensureFolder(this.options.adapter, this.pluginsPath);
337
374
  const backupPath = joinPath(this.pluginsPath, ".installing", `${basename(finalPath)}-backup-${Date.now()}`);
338
375
  const hadExisting = await this.options.adapter.exists(finalPath);
339
376
  if (hadExisting) {
340
377
  await this.options.adapter.rename(finalPath, backupPath);
341
378
  }
379
+ let moved = false;
380
+ let publishingMetadata = false;
342
381
  try {
343
382
  await this.options.adapter.rename(stagingPath, finalPath);
344
- if (hadExisting)
345
- await this.removeIfExists(backupPath);
383
+ moved = true;
384
+ publishingMetadata = true;
385
+ await metadata.publish();
346
386
  }
347
387
  catch (error) {
388
+ if (moved)
389
+ await this.removeIfExists(finalPath);
348
390
  if (hadExisting && !(await this.options.adapter.exists(finalPath))) {
349
391
  await this.options.adapter.rename(backupPath, finalPath);
350
392
  }
393
+ if (publishingMetadata)
394
+ await metadata.rollback();
351
395
  throw error;
352
396
  }
397
+ // Code and metadata are committed. Cleanup cannot reactivate the old module.
398
+ if (hadExisting) {
399
+ try {
400
+ await this.removeIfExists(backupPath);
401
+ }
402
+ catch (error) {
403
+ console.warn(`Could not remove plugin backup ${backupPath}`, error);
404
+ }
405
+ }
353
406
  }
354
407
  async removePluginCodeButKeepData(pluginPath) {
355
408
  if (!(await this.options.adapter.exists(pluginPath)))
@@ -365,7 +418,7 @@ export class VerifiedPluginInstaller {
365
418
  }
366
419
  }
367
420
  async removeCommunityPluginEnabledState(pluginId) {
368
- const enabledPath = ".obsidian/community-plugins.json";
421
+ const enabledPath = appStatePath("community-plugins.json");
369
422
  try {
370
423
  const enabled = JSON.parse(await this.options.adapter.read(enabledPath));
371
424
  if (!Array.isArray(enabled))
@@ -388,7 +441,7 @@ export class VerifiedPluginInstaller {
388
441
  }
389
442
  }
390
443
  get pluginsPath() {
391
- return this.options.pluginsPath ?? ".obsidian/plugins";
444
+ return this.options.pluginsPath ?? appStatePath("plugins");
392
445
  }
393
446
  now() {
394
447
  return (this.options.now?.() ?? new Date()).toISOString();
@@ -4,7 +4,7 @@ import { type PluginBinaryFetch, type PluginInstallerManager } from "./installer
4
4
  import type { PluginBundleVerificationMode } from "./plugin-bundle-worker";
5
5
  import { type ParsedNostrPluginBundle } from "./plugin-bundle";
6
6
  import type { VerifiedPluginBundlePayload } from "./plugin-bundle-verifier";
7
- import type { InstalledPluginRecord, PluginCatalogDetail, PluginCatalogEntry, PluginCatalogIndex, PluginCompatibilityResult, PluginDownloadStatsSummary, PluginPlatform, PluginProvenance, PluginRegistryChannel, PluginRevocationIndex, PluginRevocationRecord, PluginRegistrySource, PluginRegistrySourceKind, PluginRegistrySourceState, TrustedSigningKey } from "./types";
7
+ import type { InstalledPluginRecord, PluginCatalogDetail, PluginCatalogEntry, PluginCatalogIndex, PluginCompatibilityInput, PluginCompatibilityResult, PluginDownloadStatsSummary, PluginPlatform, PluginProvenance, PluginRegistryChannel, PluginRevocationIndex, PluginRevocationRecord, PluginRegistrySource, PluginRegistrySourceKind, PluginRegistrySourceState, TrustedSigningKey } from "./types";
8
8
  export type PluginInstallPhase = "resolving" | "fetching-catalog" | "fetching-release" | "verifying-signature" | "downloading-bundle" | "verifying-bundle" | "extracting-files" | "verifying-files" | "staging" | "installing" | "loading" | "enabling" | "complete" | "failed";
9
9
  export interface PluginInstallProgressEvent {
10
10
  pluginId: string;
@@ -73,6 +73,7 @@ export interface PluginDistributionManager {
73
73
  setSourceEnabled(sourceId: string, enabled: boolean): Promise<void>;
74
74
  search(query?: PluginSearchQuery): PluginCatalogEntry[];
75
75
  getCatalogEntry(pluginId: string): PluginCatalogEntry | undefined;
76
+ getCompatibility?(pluginId: string): PluginCompatibilityResult | null;
76
77
  getPluginDetail(pluginId: string, options?: {
77
78
  registryId?: string;
78
79
  }): Promise<PluginCatalogDetail | null>;
@@ -122,6 +123,7 @@ export interface DefaultPluginDistributionManagerOptions {
122
123
  adapter: DataAdapter;
123
124
  appVersion: string;
124
125
  platform?: PluginPlatform;
126
+ unavailablePlugins?: PluginCompatibilityInput["unavailablePlugins"];
125
127
  workspaceTrusted?: () => Promise<boolean> | boolean;
126
128
  registries?: PluginRegistrySource[];
127
129
  trustedKeys?: TrustedSigningKey[];
@@ -162,6 +164,7 @@ export declare class DefaultPluginDistributionManager extends EventDispatcher<{
162
164
  setSourceEnabled(sourceId: string, enabled: boolean): Promise<void>;
163
165
  search(query?: PluginSearchQuery): PluginCatalogEntry[];
164
166
  getCatalogEntry(pluginId: string): PluginCatalogEntry | undefined;
167
+ getCompatibility(pluginId: string): PluginCompatibilityResult | null;
165
168
  getPluginDetail(pluginId: string, options?: {
166
169
  registryId?: string;
167
170
  }): Promise<PluginCatalogDetail | null>;
@@ -1,3 +1,4 @@
1
+ import { appStatePath } from "../storage/path";
1
2
  import { EventDispatcher } from "../events";
2
3
  import { hasNativeDesktopCapability } from "../storage/desktop-native";
3
4
  import { checkCatalogEntryCompatibility, checkCatalogReleaseCompatibility, compareVersions, } from "./compatibility";
@@ -236,8 +237,7 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
236
237
  return false;
237
238
  }
238
239
  if (query.compatibleOnly) {
239
- const compatibility = checkCatalogEntryCompatibility(entry, this.compatibilityInput(false));
240
- if (!compatibility.compatible)
240
+ if (!this.getCompatibility(entry.id)?.compatible)
241
241
  return false;
242
242
  }
243
243
  return true;
@@ -246,6 +246,12 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
246
246
  getCatalogEntry(pluginId) {
247
247
  return this.catalog?.plugins.find((entry) => entry.id === pluginId);
248
248
  }
249
+ getCompatibility(pluginId) {
250
+ const entry = this.getCatalogEntry(pluginId);
251
+ return entry
252
+ ? checkCatalogEntryCompatibility(entry, this.compatibilityInput(false))
253
+ : null;
254
+ }
249
255
  async getPluginDetail(pluginId, options = {}) {
250
256
  const entry = this.entryForSource(pluginId, options.registryId);
251
257
  if (!entry)
@@ -284,7 +290,7 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
284
290
  if (!release) {
285
291
  throw new PluginDistributionError("metadata-invalid", `Plugin ${pluginId} has no requested release.`);
286
292
  }
287
- const releaseCompatibility = checkCatalogReleaseCompatibility(release, this.compatibilityInput(false));
293
+ const releaseCompatibility = checkCatalogReleaseCompatibility(release, this.compatibilityInput(false), pluginId);
288
294
  if (!releaseCompatibility.compatible) {
289
295
  throw new PluginDistributionError("compatibility-failed", `Plugin ${pluginId} release is not compatible: ${releaseCompatibility.reasons.join(", ")}`, { details: { reasons: releaseCompatibility.reasons } });
290
296
  }
@@ -488,7 +494,7 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
488
494
  });
489
495
  const targetRelease = detail?.versions[targetVersion];
490
496
  if (targetRelease) {
491
- compatibility = checkCatalogReleaseCompatibility(targetRelease, this.compatibilityInput(false));
497
+ compatibility = checkCatalogReleaseCompatibility(targetRelease, this.compatibilityInput(false), record.pluginId);
492
498
  }
493
499
  const canUpdate = hasNewerTarget && compatibility.compatible;
494
500
  const update = {
@@ -528,7 +534,7 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
528
534
  fetch: this.options.fetch,
529
535
  cache: new PluginRegistryCache({
530
536
  adapter: this.options.adapter,
531
- cachePath: `.obsidian/plugin-registry-cache/${encodeURIComponent(source.id)}/${encodeURIComponent(source.url)}.json`,
537
+ cachePath: appStatePath(`plugin-registry-cache/${encodeURIComponent(source.id)}/${encodeURIComponent(source.url)}.json`),
532
538
  }),
533
539
  });
534
540
  this.httpClients.set(key, client);
@@ -580,6 +586,7 @@ export class DefaultPluginDistributionManager extends EventDispatcher {
580
586
  appVersion: this.options.appVersion,
581
587
  platform: this.platform,
582
588
  workspaceTrusted,
589
+ unavailablePlugins: this.options.unavailablePlugins,
583
590
  };
584
591
  }
585
592
  get platform() {
@@ -1,3 +1,4 @@
1
+ import { appStatePath } from "../storage/path";
1
2
  import { pluginCatalogDetailSchema, pluginCatalogIndexSchema, pluginRevocationIndexSchema, parsePluginDistributionMetadata, } from "./schemas";
2
3
  export class PluginRegistryCache {
3
4
  options;
@@ -68,7 +69,7 @@ export class PluginRegistryCache {
68
69
  return this.write(existing.sourceUrl, existing.index, existing.details, revocations);
69
70
  }
70
71
  get cachePath() {
71
- return this.options.cachePath ?? ".obsidian/plugin-registry-cache.json";
72
+ return this.options.cachePath ?? appStatePath("plugin-registry-cache.json");
72
73
  }
73
74
  get cacheFolder() {
74
75
  const parts = this.cachePath.split("/");
@@ -1,3 +1,4 @@
1
+ import { appStatePath } from "../storage/path";
1
2
  import { dirname } from "../storage/path";
2
3
  import { ensureFolder } from "./installed-plugin-state";
3
4
  import { parsePluginDistributionMetadata, pluginRegistrySourcePreferencesStateSchema, } from "./schemas";
@@ -34,7 +35,7 @@ export class PluginRegistrySourcePreferenceStore {
34
35
  };
35
36
  }
36
37
  get path() {
37
- return this.options.path ?? ".obsidian/plugin-registry-sources.json";
38
+ return this.options.path ?? appStatePath("plugin-registry-sources.json");
38
39
  }
39
40
  now() {
40
41
  return (this.options.now?.() ?? new Date()).toISOString();
@@ -1,4 +1,4 @@
1
- import { z } from "zod";
1
+ import { z } from "zod/v3";
2
2
  export declare const registryTrustTierSchema: z.ZodEnum<["official", "community", "local"]>;
3
3
  export declare const pluginProvenanceSchema: z.ZodEnum<["bundled", "official", "community", "manual", "development"]>;
4
4
  export declare const pluginRegistryChannelSchema: z.ZodEnum<["official", "community"]>;
@@ -28,13 +28,13 @@ export declare const trustedSigningKeySchema: z.ZodObject<{
28
28
  publicKey: string;
29
29
  keyId: string;
30
30
  alg: "ed25519";
31
- trustTier: "community" | "official" | "local";
31
+ trustTier: "local" | "community" | "official";
32
32
  expiresAt?: string | undefined;
33
33
  }, {
34
34
  publicKey: string;
35
35
  keyId: string;
36
36
  alg: "ed25519";
37
- trustTier: "community" | "official" | "local";
37
+ trustTier: "local" | "community" | "official";
38
38
  expiresAt?: string | undefined;
39
39
  }>;
40
40
  export declare const remoteFileReferenceSchema: z.ZodObject<{
@@ -524,16 +524,19 @@ export declare const pluginContributionSummarySchema: z.ZodObject<{
524
524
  }, z.ZodTypeAny, "passthrough">>, "many">>;
525
525
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
526
526
  id: z.ZodString;
527
+ fileIcon: z.ZodOptional<z.ZodString>;
527
528
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
528
529
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
529
530
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
530
531
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
531
532
  id: z.ZodString;
533
+ fileIcon: z.ZodOptional<z.ZodString>;
532
534
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
533
535
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
534
536
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
535
537
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
536
538
  id: z.ZodString;
539
+ fileIcon: z.ZodOptional<z.ZodString>;
537
540
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
538
541
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
539
542
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -581,16 +584,19 @@ export declare const pluginContributionSummarySchema: z.ZodObject<{
581
584
  }, z.ZodTypeAny, "passthrough">>, "many">>;
582
585
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
583
586
  id: z.ZodString;
587
+ fileIcon: z.ZodOptional<z.ZodString>;
584
588
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
585
589
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
586
590
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
587
591
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
588
592
  id: z.ZodString;
593
+ fileIcon: z.ZodOptional<z.ZodString>;
589
594
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
590
595
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
591
596
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
592
597
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
593
598
  id: z.ZodString;
599
+ fileIcon: z.ZodOptional<z.ZodString>;
594
600
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
595
601
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
596
602
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -638,16 +644,19 @@ export declare const pluginContributionSummarySchema: z.ZodObject<{
638
644
  }, z.ZodTypeAny, "passthrough">>, "many">>;
639
645
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
640
646
  id: z.ZodString;
647
+ fileIcon: z.ZodOptional<z.ZodString>;
641
648
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
642
649
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
643
650
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
644
651
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
645
652
  id: z.ZodString;
653
+ fileIcon: z.ZodOptional<z.ZodString>;
646
654
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
647
655
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
648
656
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
649
657
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
650
658
  id: z.ZodString;
659
+ fileIcon: z.ZodOptional<z.ZodString>;
651
660
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
652
661
  extensions: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
653
662
  mimeTypes: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
@@ -687,12 +696,12 @@ export declare const pluginCatalogEntrySchema: z.ZodEffects<z.ZodUnknown, {
687
696
  description: string;
688
697
  name: string;
689
698
  status: "active" | "pending" | "disabled" | "deprecated" | "revoked";
699
+ id: string;
690
700
  owner: {
691
701
  name: string;
692
702
  verified: boolean;
693
703
  url?: string | undefined;
694
704
  };
695
- id: string;
696
705
  author: string;
697
706
  badges: string[];
698
707
  minAppVersion: string;
@@ -756,14 +765,17 @@ export declare const pluginCatalogEntrySchema: z.ZodEffects<z.ZodUnknown, {
756
765
  contributes?: z.objectOutputType<{
757
766
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
758
767
  id: z.ZodString;
768
+ fileIcon: z.ZodOptional<z.ZodString>;
759
769
  label: z.ZodOptional<z.ZodString>;
760
770
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
761
771
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
762
772
  id: z.ZodString;
773
+ fileIcon: z.ZodOptional<z.ZodString>;
763
774
  label: z.ZodOptional<z.ZodString>;
764
775
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
765
776
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
766
777
  id: z.ZodString;
778
+ fileIcon: z.ZodOptional<z.ZodString>;
767
779
  label: z.ZodOptional<z.ZodString>;
768
780
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
769
781
  }, z.ZodTypeAny, "passthrough">>, "many">>;
@@ -782,12 +794,12 @@ export declare const pluginCatalogIndexSchema: z.ZodEffects<z.ZodUnknown, {
782
794
  description: string;
783
795
  name: string;
784
796
  status: "active" | "pending" | "disabled" | "deprecated" | "revoked";
797
+ id: string;
785
798
  owner: {
786
799
  name: string;
787
800
  verified: boolean;
788
801
  url?: string | undefined;
789
802
  };
790
- id: string;
791
803
  author: string;
792
804
  badges: string[];
793
805
  minAppVersion: string;
@@ -851,14 +863,17 @@ export declare const pluginCatalogIndexSchema: z.ZodEffects<z.ZodUnknown, {
851
863
  contributes?: z.objectOutputType<{
852
864
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
853
865
  id: z.ZodString;
866
+ fileIcon: z.ZodOptional<z.ZodString>;
854
867
  label: z.ZodOptional<z.ZodString>;
855
868
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
856
869
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
857
870
  id: z.ZodString;
871
+ fileIcon: z.ZodOptional<z.ZodString>;
858
872
  label: z.ZodOptional<z.ZodString>;
859
873
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
860
874
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
861
875
  id: z.ZodString;
876
+ fileIcon: z.ZodOptional<z.ZodString>;
862
877
  label: z.ZodOptional<z.ZodString>;
863
878
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
864
879
  }, z.ZodTypeAny, "passthrough">>, "many">>;
@@ -2617,6 +2632,7 @@ export declare const pluginCatalogDetailSchema: z.ZodEffects<z.ZodUnknown, {
2617
2632
  description: string;
2618
2633
  name: string;
2619
2634
  status: "active" | "pending" | "disabled" | "deprecated" | "revoked";
2635
+ id: string;
2620
2636
  versions: Record<string, {
2621
2637
  version: string;
2622
2638
  bundle: {
@@ -2641,7 +2657,6 @@ export declare const pluginCatalogDetailSchema: z.ZodEffects<z.ZodUnknown, {
2641
2657
  verified: boolean;
2642
2658
  url?: string | undefined;
2643
2659
  };
2644
- id: string;
2645
2660
  schemaVersion: 2;
2646
2661
  author: string;
2647
2662
  channel: "community" | "official";
@@ -2827,14 +2842,17 @@ export declare const pluginCatalogDetailSchema: z.ZodEffects<z.ZodUnknown, {
2827
2842
  contributes?: z.objectOutputType<{
2828
2843
  editorViews: z.ZodOptional<z.ZodArray<z.ZodObject<{
2829
2844
  id: z.ZodString;
2845
+ fileIcon: z.ZodOptional<z.ZodString>;
2830
2846
  label: z.ZodOptional<z.ZodString>;
2831
2847
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2832
2848
  }, "passthrough", z.ZodTypeAny, z.objectOutputType<{
2833
2849
  id: z.ZodString;
2850
+ fileIcon: z.ZodOptional<z.ZodString>;
2834
2851
  label: z.ZodOptional<z.ZodString>;
2835
2852
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2836
2853
  }, z.ZodTypeAny, "passthrough">, z.objectInputType<{
2837
2854
  id: z.ZodString;
2855
+ fileIcon: z.ZodOptional<z.ZodString>;
2838
2856
  label: z.ZodOptional<z.ZodString>;
2839
2857
  filenamePatterns: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
2840
2858
  }, z.ZodTypeAny, "passthrough">>, "many">>;
@@ -3107,8 +3125,8 @@ export declare const pluginReleaseManifestSchema: z.ZodObject<{
3107
3125
  size: z.ZodNumber;
3108
3126
  }, z.ZodTypeAny, "passthrough">[];
3109
3127
  type: "lapis.plugin.release";
3110
- schemaVersion: 1;
3111
3128
  version: string;
3129
+ schemaVersion: 1;
3112
3130
  pluginId: string;
3113
3131
  channel: "community" | "official";
3114
3132
  compatibility: {
@@ -3200,8 +3218,8 @@ export declare const pluginReleaseManifestSchema: z.ZodObject<{
3200
3218
  size: z.ZodNumber;
3201
3219
  }, z.ZodTypeAny, "passthrough">[];
3202
3220
  type: "lapis.plugin.release";
3203
- schemaVersion: 1;
3204
3221
  version: string;
3222
+ schemaVersion: 1;
3205
3223
  pluginId: string;
3206
3224
  channel: "community" | "official";
3207
3225
  compatibility: {
@@ -1,5 +1,5 @@
1
1
  import { normalizeRegistryCatalogEntry, normalizeRegistryCatalogIndex, normalizeRegistryCatalogDetail, } from "../registry-schema/index.js";
2
- import { z } from "zod";
2
+ import { z } from "zod/v3";
3
3
  import { PluginDistributionError } from "./errors";
4
4
  const isoDate = z.string().datetime({ offset: true });
5
5
  const utcDate = z
@@ -174,6 +174,7 @@ export const pluginContributionSummarySchema = z
174
174
  .array(z
175
175
  .object({
176
176
  id: nonEmpty,
177
+ fileIcon: z.string().optional(),
177
178
  filenamePatterns: z.array(nonEmpty).optional(),
178
179
  extensions: z.array(nonEmpty).optional(),
179
180
  mimeTypes: z.array(nonEmpty).optional(),
@@ -42,6 +42,7 @@ export interface PluginContributionSummary {
42
42
  }>;
43
43
  editorViews?: Array<{
44
44
  id: string;
45
+ fileIcon?: string;
45
46
  filenamePatterns?: string[];
46
47
  extensions?: string[];
47
48
  mimeTypes?: string[];
@@ -321,7 +322,10 @@ export interface PluginCompatibilityInput {
321
322
  appVersion: string;
322
323
  platform: PluginPlatform;
323
324
  workspaceTrusted?: boolean;
325
+ unavailablePlugins?: PluginHostRestrictions;
324
326
  }
327
+ /** App-owned exclusions override older manifests' compatibility claims. */
328
+ export type PluginHostRestrictions = Readonly<Record<string, "desktop-only" | "platform-unsupported">>;
325
329
  export interface PluginCompatibilityResult {
326
330
  compatible: boolean;
327
331
  reasons: Array<"app-version-too-old" | "platform-unsupported" | "desktop-only" | "workspace-trust-required" | "revoked">;