@lapis-notes/api 0.1.3 → 0.2.0

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 (199) hide show
  1. package/CHANGELOG.md +304 -13
  2. package/README.md +19 -0
  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 +24 -11
  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 +23 -3
  31. package/dist/context.svelte.js +68 -30
  32. package/dist/diagnostics.d.ts +8 -0
  33. package/dist/diagnostics.js +6 -0
  34. package/dist/editor-save-context.d.ts +35 -0
  35. package/dist/editor-save-context.js +132 -0
  36. package/dist/editor-view-registry.d.ts +10 -0
  37. package/dist/editor-view-registry.js +44 -4
  38. package/dist/editor.svelte.d.ts +19 -1
  39. package/dist/editor.svelte.js +528 -55
  40. package/dist/embed-registry.d.ts +10 -0
  41. package/dist/embed-registry.js +15 -0
  42. package/dist/file-editing-failures.d.ts +2 -0
  43. package/dist/file-editing-failures.js +29 -0
  44. package/dist/file-editing-native-issues.d.ts +19 -0
  45. package/dist/file-editing-native-issues.js +65 -0
  46. package/dist/file-editing-sessions.d.ts +76 -0
  47. package/dist/file-editing-sessions.js +367 -0
  48. package/dist/file-manager.d.ts +9 -1
  49. package/dist/file-manager.js +45 -3
  50. package/dist/file-state-store.d.ts +5 -0
  51. package/dist/file-state-store.js +34 -0
  52. package/dist/file-text-conflicts.d.ts +25 -0
  53. package/dist/file-text-conflicts.js +35 -0
  54. package/dist/file-text-mutation.d.ts +13 -0
  55. package/dist/file-text-mutation.js +100 -0
  56. package/dist/file-text-persistence.d.ts +36 -0
  57. package/dist/file-text-persistence.js +234 -0
  58. package/dist/file-text-state-writer.d.ts +34 -0
  59. package/dist/file-text-state-writer.js +183 -0
  60. package/dist/files-configuration.d.ts +20 -0
  61. package/dist/files-configuration.js +47 -0
  62. package/dist/generated/plugin-host-modules.schema.generated.d.ts +114 -34
  63. package/dist/generated/plugin-host-modules.schema.generated.js +132 -34
  64. package/dist/glob.d.ts +3 -0
  65. package/dist/glob.js +2 -1
  66. package/dist/icons/changes.d.ts +3 -0
  67. package/dist/icons/changes.js +16 -0
  68. package/dist/icons/index.d.ts +5 -1
  69. package/dist/icons/index.js +44 -3
  70. package/dist/icons/registry.d.ts +35 -0
  71. package/dist/icons/registry.js +154 -0
  72. package/dist/icons/safe-svg.d.ts +2 -0
  73. package/dist/icons/safe-svg.js +44 -0
  74. package/dist/index.d.ts +6 -1
  75. package/dist/index.js +4 -1
  76. package/dist/lapis-extension.d.ts +6 -2
  77. package/dist/lapis-extension.js +2 -0
  78. package/dist/metadata-store.d.ts +23 -0
  79. package/dist/metadata-store.js +1 -0
  80. package/dist/metadata.svelte.js +45 -2
  81. package/dist/native-background-indexing.d.ts +48 -0
  82. package/dist/native-background-indexing.js +208 -0
  83. package/dist/native-database-protocol.d.ts +29 -0
  84. package/dist/native-database-protocol.js +1 -0
  85. package/dist/native-editing-intent-lease.d.ts +17 -0
  86. package/dist/native-editing-intent-lease.js +188 -0
  87. package/dist/native-editing-intents-protocol.d.ts +70 -0
  88. package/dist/native-editing-intents-protocol.js +1 -0
  89. package/dist/native-editing-intents.d.ts +8 -0
  90. package/dist/native-editing-intents.js +1 -0
  91. package/dist/native-editing-problems.d.ts +3 -0
  92. package/dist/native-editing-problems.js +180 -0
  93. package/dist/native-editing-recovery.d.ts +24 -0
  94. package/dist/native-editing-recovery.js +198 -0
  95. package/dist/native-editing-session.d.ts +51 -0
  96. package/dist/native-editing-session.js +251 -0
  97. package/dist/native-history-index-protocol.d.ts +80 -0
  98. package/dist/native-history-index-protocol.js +1 -0
  99. package/dist/native-history-index.d.ts +6 -0
  100. package/dist/native-history-index.js +1 -0
  101. package/dist/native-indexing-protocol.d.ts +63 -0
  102. package/dist/native-indexing-protocol.js +1 -0
  103. package/dist/native-participant-protocol.d.ts +67 -0
  104. package/dist/native-participant-protocol.js +3 -0
  105. package/dist/native-queue-protocol.d.ts +73 -0
  106. package/dist/native-queue-protocol.js +3 -0
  107. package/dist/native-tasks-protocol.d.ts +264 -0
  108. package/dist/native-tasks-protocol.js +1 -0
  109. package/dist/native-tasks.d.ts +35 -0
  110. package/dist/native-tasks.js +79 -0
  111. package/dist/native-turn-protocol.d.ts +81 -0
  112. package/dist/native-turn-protocol.js +1 -0
  113. package/dist/native-turns.d.ts +29 -0
  114. package/dist/native-turns.js +2 -0
  115. package/dist/plugin-asset-server.js +3 -2
  116. package/dist/plugin-dependency-resolver.d.ts +1 -1
  117. package/dist/plugin-distribution/compatibility.d.ts +1 -1
  118. package/dist/plugin-distribution/compatibility.js +10 -5
  119. package/dist/plugin-distribution/installed-plugin-state.js +2 -1
  120. package/dist/plugin-distribution/installer.d.ts +6 -0
  121. package/dist/plugin-distribution/installer.js +90 -37
  122. package/dist/plugin-distribution/manager.d.ts +4 -1
  123. package/dist/plugin-distribution/manager.js +12 -5
  124. package/dist/plugin-distribution/registry-cache.js +2 -1
  125. package/dist/plugin-distribution/registry-source-state.js +2 -1
  126. package/dist/plugin-distribution/schemas.d.ts +26 -8
  127. package/dist/plugin-distribution/schemas.js +2 -1
  128. package/dist/plugin-distribution/types.d.ts +4 -0
  129. package/dist/plugin-manager.d.ts +21 -5
  130. package/dist/plugin-manager.js +228 -64
  131. package/dist/plugin-runtime-entry.d.ts +1 -1
  132. package/dist/plugin-runtime-entry.js +16 -0
  133. package/dist/plugin.d.ts +19 -3
  134. package/dist/plugin.js +110 -44
  135. package/dist/registry-schema/catalog-v1.d.ts +60 -9
  136. package/dist/registry-schema/catalog-v1.js +2 -1
  137. package/dist/registry-schema/catalog-v2.d.ts +60 -9
  138. package/dist/registry-schema/catalog-v2.js +1 -1
  139. package/dist/search-result-provider.d.ts +95 -0
  140. package/dist/search-result-provider.js +42 -0
  141. package/dist/status-bar.svelte.d.ts +26 -20
  142. package/dist/status-bar.svelte.js +1 -0
  143. package/dist/storage/app-database.d.ts +9 -23
  144. package/dist/storage/app-database.js +41 -34
  145. package/dist/storage/desktop-native.d.ts +5 -3
  146. package/dist/storage/desktop-native.js +59 -6
  147. package/dist/storage/directory-watcher.d.ts +27 -0
  148. package/dist/storage/directory-watcher.js +201 -0
  149. package/dist/storage/fs.d.ts +14 -27
  150. package/dist/storage/fs.js +2 -168
  151. package/dist/storage/index-projection.js +33 -15
  152. package/dist/storage/memory-vault-adapter.d.ts +2 -0
  153. package/dist/storage/memory-vault-adapter.js +14 -1
  154. package/dist/storage/path.d.ts +6 -2
  155. package/dist/storage/path.js +8 -2
  156. package/dist/storage/projection-query-candidates.d.ts +3 -0
  157. package/dist/storage/projection-query-candidates.js +69 -0
  158. package/dist/storage/remote-app-database.d.ts +2 -2
  159. package/dist/storage/remote-app-database.js +1 -1
  160. package/dist/storage/runtime.d.ts +3 -0
  161. package/dist/storage/runtime.js +3 -0
  162. package/dist/storage/search-embedding-record.d.ts +3 -0
  163. package/dist/storage/search-embedding-record.js +18 -0
  164. package/dist/storage/turso-app-database.d.ts +2 -0
  165. package/dist/storage/turso-app-database.js +67 -30
  166. package/dist/storage/vault-listing.d.ts +3 -0
  167. package/dist/storage/vault-listing.js +36 -0
  168. package/dist/storage/vault-reconciliation.d.ts +19 -0
  169. package/dist/storage/vault-reconciliation.js +137 -0
  170. package/dist/storage/vault-session.d.ts +8 -0
  171. package/dist/storage/vault-session.js +10 -2
  172. package/dist/storage/vault.svelte.d.ts +53 -4
  173. package/dist/storage/vault.svelte.js +223 -87
  174. package/dist/sync-editor-flush.d.ts +6 -0
  175. package/dist/sync-editor-flush.js +22 -0
  176. package/dist/sync-host-local.d.ts +15 -0
  177. package/dist/sync-host-local.js +228 -0
  178. package/dist/sync-host-security.d.ts +24 -9
  179. package/dist/sync-host-security.js +47 -14
  180. package/dist/sync-host.d.ts +33 -4
  181. package/dist/sync-host.js +160 -10
  182. package/dist/sync-repository.d.ts +106 -0
  183. package/dist/sync-repository.js +18 -0
  184. package/dist/vault-change-views.d.ts +4 -0
  185. package/dist/vault-change-views.js +43 -0
  186. package/dist/vault-changes.d.ts +37 -0
  187. package/dist/vault-changes.js +228 -0
  188. package/dist/view.svelte.d.ts +182 -149
  189. package/dist/view.svelte.js +39 -21
  190. package/dist/workspace-close-guard.d.ts +3 -0
  191. package/dist/workspace-close-guard.js +35 -0
  192. package/dist/workspace-host-internal.js +10 -2
  193. package/dist/workspace-layout-normalizer.d.ts +4 -2
  194. package/dist/workspace-layout-normalizer.js +5 -5
  195. package/dist/workspace-view-document.d.ts +3 -0
  196. package/dist/workspace-view-document.js +12 -0
  197. package/dist/workspace.svelte.d.ts +13 -2
  198. package/dist/workspace.svelte.js +371 -82
  199. package/package.json +61 -8
@@ -0,0 +1,154 @@
1
+ import { convertParsedSVG, getIconData, iconToHTML, iconToSVG, parseSVGContent, replaceIDs, } from "@iconify/utils";
2
+ import { resolveRegisteredIcon } from "./index";
3
+ import { iconRevision, onIconsChanged } from "./changes";
4
+ import { assertSafeIconSvg } from "./safe-svg";
5
+ function loadPack(input) {
6
+ try {
7
+ return Promise.resolve("icons" in input ? input.icons : input.loader());
8
+ }
9
+ catch (error) {
10
+ return Promise.reject(error);
11
+ }
12
+ }
13
+ function namespace(owner) {
14
+ if (!owner.trim())
15
+ throw new Error("Icon owner must not be empty.");
16
+ return ("plugin-" +
17
+ [...new TextEncoder().encode(owner)]
18
+ .map((x) => x.toString(16).padStart(2, "0"))
19
+ .join(""));
20
+ }
21
+ function localName(id) {
22
+ if (!/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(id))
23
+ throw new Error("Use a lowercase icon name with hyphens.");
24
+ return id;
25
+ }
26
+ function render(data) {
27
+ const rendered = iconToSVG(data);
28
+ const svg = iconToHTML(replaceIDs(rendered.body), rendered.attributes);
29
+ assertSafeIconSvg(svg);
30
+ return svg;
31
+ }
32
+ /**
33
+ * App-owned artwork with an explicit compatibility fallback to legacy global
34
+ * icons.
35
+ */
36
+ export class IconRegistry {
37
+ icons = new Map();
38
+ packs = new Map();
39
+ listeners = new Set();
40
+ cache = new Map();
41
+ revision = 0;
42
+ legacyRevision = iconRevision();
43
+ stopLegacy;
44
+ disposed = false;
45
+ subscribe = (listener) => {
46
+ if (this.disposed)
47
+ return () => { };
48
+ if (!this.stopLegacy)
49
+ this.stopLegacy = onIconsChanged(() => this.changed());
50
+ this.listeners.add(listener);
51
+ return () => {
52
+ this.listeners.delete(listener);
53
+ if (!this.listeners.size) {
54
+ this.stopLegacy?.();
55
+ this.stopLegacy = undefined;
56
+ }
57
+ };
58
+ };
59
+ changed() {
60
+ this.revision++;
61
+ this.cache.clear();
62
+ this.legacyRevision = iconRevision();
63
+ for (const listener of [...this.listeners])
64
+ listener();
65
+ }
66
+ registerIcon(owner, id, svg) {
67
+ if (this.disposed)
68
+ throw new Error("Icon registry is disposed.");
69
+ assertSafeIconSvg(svg);
70
+ const parsed = parseSVGContent(svg);
71
+ const data = parsed && convertParsedSVG(parsed);
72
+ if (!data)
73
+ throw new Error("Invalid icon SVG.");
74
+ const name = `${namespace(owner)}:${localName(id)}`;
75
+ const entry = { svg: render(data) };
76
+ this.icons.set(name, entry);
77
+ this.changed();
78
+ return {
79
+ id: name,
80
+ dispose: () => {
81
+ if (this.icons.get(name) === entry) {
82
+ this.icons.delete(name);
83
+ this.changed();
84
+ }
85
+ },
86
+ };
87
+ }
88
+ registerIconPack(owner, input) {
89
+ if (this.disposed)
90
+ throw new Error("Icon registry is disposed.");
91
+ const prefix = `${namespace(owner)}-${localName(input.name)}`;
92
+ const entry = { input };
93
+ this.packs.set(prefix, entry);
94
+ this.changed();
95
+ return {
96
+ prefix,
97
+ dispose: () => {
98
+ if (this.packs.get(prefix) === entry) {
99
+ this.packs.delete(prefix);
100
+ this.changed();
101
+ }
102
+ },
103
+ };
104
+ }
105
+ resolveIcon(name) {
106
+ if (this.disposed)
107
+ return Promise.resolve({ status: "missing" });
108
+ if (this.legacyRevision !== iconRevision())
109
+ this.changed();
110
+ const cached = this.cache.get(name);
111
+ if (cached)
112
+ return cached;
113
+ const revision = this.revision;
114
+ const pending = this.lookup(name)
115
+ .then((result) => this.disposed || revision !== this.revision
116
+ ? { status: "missing" }
117
+ : result)
118
+ .catch(() => ({ status: "failed" }));
119
+ this.cache.set(name, pending);
120
+ return pending;
121
+ }
122
+ resolve = async (name) => {
123
+ const result = await this.resolveIcon(name);
124
+ return result.status === "found" ? result.svg : null;
125
+ };
126
+ async lookup(name) {
127
+ const icon = this.icons.get(name);
128
+ if (icon)
129
+ return { status: "found", svg: icon.svg };
130
+ const [prefix, id, ...extra] = name.split(":");
131
+ const pack = this.packs.get(prefix);
132
+ if (pack && id && extra.length === 0) {
133
+ pack.promise ??= loadPack(pack.input);
134
+ const data = getIconData(await pack.promise, id);
135
+ return data
136
+ ? { status: "found", svg: render(data) }
137
+ : { status: "missing" };
138
+ }
139
+ // Owned IDs never resolve through a different App's compatibility registry.
140
+ if (/^plugin-(?:[a-f0-9]{2})+(?:-[a-z0-9]+(?:-[a-z0-9]+)*)?:/u.test(name))
141
+ return { status: "missing" };
142
+ return resolveRegisteredIcon(name);
143
+ }
144
+ dispose() {
145
+ this.disposed = true;
146
+ this.stopLegacy?.();
147
+ this.stopLegacy = undefined;
148
+ this.icons.clear();
149
+ this.packs.clear();
150
+ this.cache.clear();
151
+ this.listeners.clear();
152
+ this.revision++;
153
+ }
154
+ }
@@ -0,0 +1,2 @@
1
+ /** Reject executable/external content before a host displays plugin SVG markup. */
2
+ export declare function assertSafeIconSvg(svg: string): void;
@@ -0,0 +1,44 @@
1
+ /** Reject executable/external content before a host displays plugin SVG markup. */
2
+ export function assertSafeIconSvg(svg) {
3
+ const tags = new Set([
4
+ "svg",
5
+ "g",
6
+ "path",
7
+ "rect",
8
+ "circle",
9
+ "ellipse",
10
+ "line",
11
+ "polyline",
12
+ "polygon",
13
+ "defs",
14
+ "lineargradient",
15
+ "radialgradient",
16
+ "stop",
17
+ "clippath",
18
+ "mask",
19
+ "pattern",
20
+ "symbol",
21
+ "use",
22
+ "title",
23
+ "desc",
24
+ ]);
25
+ if (/xml:base\s*=|<!|<\?|&#|\\|javascript\s*:|data\s*:/iu.test(svg))
26
+ throw new Error("Unsafe icon SVG.");
27
+ for (const match of svg.matchAll(/<\/?\s*([\w:-]+)/gu)) {
28
+ if (!tags.has(match[1].toLowerCase()))
29
+ throw new Error("Unsupported icon SVG element.");
30
+ }
31
+ if (/\s(?:on[\w:-]+)\s*=/iu.test(svg) ||
32
+ /@import|expression\s*\(/iu.test(svg))
33
+ throw new Error("Executable icon SVG.");
34
+ for (const match of svg.matchAll(/(?:\b(?:xlink:)?href\s*=\s*)(["'])(.*?)\1/giu)) {
35
+ if (!/^#[\w:.-]+$/u.test(match[2]))
36
+ throw new Error("External icon SVG reference.");
37
+ }
38
+ if (/\b(?:xlink:)?href\s*=\s*[^\s"']/iu.test(svg))
39
+ throw new Error("Unquoted icon SVG reference.");
40
+ for (const match of svg.matchAll(/url\s*\((.*?)\)/giu)) {
41
+ if (!/^\s*["']?#[\w:.-]+["']?\s*$/u.test(match[1]))
42
+ throw new Error("External icon SVG URL.");
43
+ }
44
+ }
package/dist/index.d.ts CHANGED
@@ -1,11 +1,15 @@
1
1
  import "./enhance";
2
2
  export * from "./editor.svelte";
3
+ export * from "./file-editing-sessions";
4
+ export { bindNativeEditingProblems } from "./native-editing-problems";
5
+ export type { NativeEditingIntents } from "./native-editing-intents";
3
6
  export * from "./events";
4
7
  export * from "./components/editor/extensions/autocomplete";
5
8
  export * from "./components/editor/extensions/lint";
6
9
  export * from "./components/editor/language-service";
7
10
  export * from "./menu.svelte";
8
11
  export * from "./cache.svelte";
12
+ export type { NativeBackgroundIndexing, NativeBackgroundIndexingObserver, NativeIndexingProgressPresentation, } from "./native-background-indexing";
9
13
  export * from "./plugin";
10
14
  export * from "./plugin-capability-facade";
11
15
  export * from "./sync-host";
@@ -53,7 +57,7 @@ export * from "./configuration-option-source-registry";
53
57
  export * from "./configuration-option-source-providers";
54
58
  export * from "./plugin-settings";
55
59
  export * from "./editor-view-registry";
56
- export * from "./glob";
60
+ export { normalizeGlobPath, normalizeEditorAssociationGlob, validateEditorAssociationGlob, matchesEditorAssociationGlob, hasEditorAssociationGlobMagic, compareEditorAssociationPatternSpecificity, type EditorAssociationGlobOptions, type EditorAssociationGlobValidation, } from "./glob";
57
61
  export * from "./platform";
58
62
  export * from "./suggest";
59
63
  export * from "./markdown";
@@ -82,6 +86,7 @@ export * from "./promise-worker";
82
86
  export * from "./search";
83
87
  export * from "./search-query";
84
88
  export * from "./search-document-provider";
89
+ export * from "./search-result-provider";
85
90
  export * from "./daily-document-provider";
86
91
  export * from "./loaders";
87
92
  export * from "./bases";
package/dist/index.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import "./enhance";
2
2
  export * from "./editor.svelte";
3
+ export * from "./file-editing-sessions";
4
+ export { bindNativeEditingProblems } from "./native-editing-problems";
3
5
  export * from "./events";
4
6
  export * from "./components/editor/extensions/autocomplete";
5
7
  export * from "./components/editor/extensions/lint";
@@ -53,7 +55,7 @@ export * from "./configuration-option-source-registry";
53
55
  export * from "./configuration-option-source-providers";
54
56
  export * from "./plugin-settings";
55
57
  export * from "./editor-view-registry";
56
- export * from "./glob";
58
+ export { normalizeGlobPath, normalizeEditorAssociationGlob, validateEditorAssociationGlob, matchesEditorAssociationGlob, hasEditorAssociationGlobMagic, compareEditorAssociationPatternSpecificity, } from "./glob";
57
59
  export * from "./platform";
58
60
  export * from "./suggest";
59
61
  export * from "./markdown";
@@ -82,6 +84,7 @@ export * from "./promise-worker";
82
84
  export * from "./search";
83
85
  export * from "./search-query";
84
86
  export * from "./search-document-provider";
87
+ export * from "./search-result-provider";
85
88
  export * from "./daily-document-provider";
86
89
  export * from "./loaders";
87
90
  export * from "./bases";
@@ -54,6 +54,7 @@ export interface LapisEditorViewContribution {
54
54
  viewType?: string;
55
55
  label: string;
56
56
  description?: string;
57
+ fileIcon?: string;
57
58
  filenamePatterns: string[];
58
59
  priority?: "default" | "option" | "exclusive";
59
60
  pluginId?: string;
@@ -86,7 +87,7 @@ export interface LapisStatusBarItemContribution {
86
87
  id: string;
87
88
  text?: string;
88
89
  icon?: string;
89
- alignment?: "left" | "right";
90
+ alignment?: "left" | "center" | "right";
90
91
  priority?: number;
91
92
  tooltip?: string;
92
93
  command?: string;
@@ -130,7 +131,10 @@ export interface LapisExtensionManifest {
130
131
  };
131
132
  };
132
133
  database?: {
133
- /** Query-native access avoids the one-release full MetadataCache snapshot lease. */
134
+ /**
135
+ * Query-native access avoids the one-release full MetadataCache snapshot
136
+ * lease.
137
+ */
134
138
  metadataAccess?: "queries" | "snapshot";
135
139
  };
136
140
  permissions?: LapisExtensionPermission[];
@@ -566,6 +566,7 @@ function isEditorViewContribution(value) {
566
566
  Array.isArray(value.filenamePatterns) &&
567
567
  value.filenamePatterns.length > 0 &&
568
568
  value.filenamePatterns.every((entry) => typeof entry === "string") &&
569
+ (value.fileIcon === undefined || typeof value.fileIcon === "string") &&
569
570
  (value.viewType === undefined || typeof value.viewType === "string") &&
570
571
  (value.description === undefined ||
571
572
  typeof value.description === "string") &&
@@ -595,6 +596,7 @@ function isStatusBarItemContribution(value) {
595
596
  (typeof value.text === "string" || typeof value.icon === "string") &&
596
597
  (value.alignment === undefined ||
597
598
  value.alignment === "left" ||
599
+ value.alignment === "center" ||
598
600
  value.alignment === "right") &&
599
601
  (value.priority === undefined || typeof value.priority === "number") &&
600
602
  (value.tooltip === undefined || typeof value.tooltip === "string") &&
@@ -0,0 +1,23 @@
1
+ /** Host persistence only; implementations retain authorization and lifecycle ownership. */
2
+ export interface HostMetadataStore {
3
+ getMeta<T = unknown>(key: string): Promise<T | undefined>;
4
+ setMeta(key: string, value: unknown): Promise<void>;
5
+ transactMeta(transaction: AppDatabaseMetaTransaction): Promise<AppDatabaseMetaTransactionResult>;
6
+ }
7
+ export interface AppDatabaseMetaCheck {
8
+ key: string;
9
+ exists?: boolean;
10
+ value?: unknown;
11
+ }
12
+ export interface AppDatabaseMetaMutation {
13
+ key: string;
14
+ value?: unknown;
15
+ delete?: boolean;
16
+ }
17
+ export interface AppDatabaseMetaTransaction {
18
+ checks?: AppDatabaseMetaCheck[];
19
+ mutations: AppDatabaseMetaMutation[];
20
+ }
21
+ export interface AppDatabaseMetaTransactionResult {
22
+ applied: boolean;
23
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,7 @@
1
+ import { FileTextStateWriter } from "./file-text-state-writer";
2
+ import { TFile as VaultFile } from "./storage/fs";
1
3
  import { inferMetadataPropertyType, inferMetadataType, normalizeMetadataValue, } from "./metadata-value";
2
- import { joinPath, } from "./storage";
4
+ import { joinPath, normalizePath, } from "./storage";
3
5
  import { debounce, get, has, set, unset } from "lodash-es";
4
6
  import { onMount } from "svelte";
5
7
  function notifyMetadataTypeError(message) {
@@ -21,6 +23,8 @@ export class MetadataTypeManager {
21
23
  propertiesLoading = $state(false);
22
24
  queryError = $state(null);
23
25
  propertiesGeneration = 0;
26
+ typesBaseline;
27
+ typesWriter;
24
28
  valueGenerations = new Map();
25
29
  schedulePropertiesRefresh = debounce(() => void this.updateProperties(), 50);
26
30
  constructor(app) {
@@ -40,6 +44,7 @@ export class MetadataTypeManager {
40
44
  this.app.metadataCache.offref(metadataHandler);
41
45
  this.app.metadataCache.offref(indexHandler);
42
46
  this.schedulePropertiesRefresh.cancel();
47
+ this.typesWriter?.dispose();
43
48
  };
44
49
  });
45
50
  this.load();
@@ -112,8 +117,13 @@ export class MetadataTypeManager {
112
117
  load() {
113
118
  const promises = [];
114
119
  const file = this.app.vault.getFileByPath(joinPath(this.app.props.configPath, "../", "types.json"));
120
+ if (!file && !this.typesWriter)
121
+ this.typesBaseline = null;
115
122
  if (file) {
116
123
  promises.push(this.app.vault.read(file).then((data) => {
124
+ if (this.typesWriter)
125
+ return;
126
+ this.typesBaseline = data;
117
127
  try {
118
128
  const typeData = JSON.parse(data);
119
129
  if ("types" in typeData) {
@@ -166,12 +176,43 @@ export class MetadataTypeManager {
166
176
  return "unknown";
167
177
  return type === "checkbox" ? "checkbox" : "unknown";
168
178
  }
169
- save = debounce(() => {
179
+ saveLegacy = debounce(() => {
170
180
  const file = this.app.vault.getFileByPath(joinPath(this.app.props.configPath, "../", "types.json"));
171
181
  if (file) {
172
182
  return this.app.vault.modify(file, JSON.stringify(this.toJSON(), null, 2));
173
183
  }
174
184
  }, 500);
185
+ save = () => {
186
+ if (!this.app.vault.supportsConditionalTextWrites) {
187
+ this.saveLegacy();
188
+ return;
189
+ }
190
+ void this.getTypesWriter().set(JSON.stringify(this.toJSON(), null, 2))
191
+ .catch((error) => notifyMetadataTypeError(`Failed to save types.json: ${error}`));
192
+ };
193
+ getTypesWriter() {
194
+ if (this.typesWriter)
195
+ return this.typesWriter;
196
+ const path = normalizePath(joinPath(this.app.props.configPath, "../", "types.json"));
197
+ const file = this.app.vault.getFileByPath(path) ??
198
+ new VaultFile(path, { ctime: 0, mtime: 0, size: 0 }, null);
199
+ this.typesWriter ??= new FileTextStateWriter(this.app, file, this.typesBaseline ?? null, (text) => {
200
+ for (const key of Object.keys(this.types))
201
+ delete this.types[key];
202
+ try {
203
+ const value = JSON.parse(text || '{"types":{}}');
204
+ for (const [name, type] of Object.entries(value.types ?? {})) {
205
+ if (typeof type === "string")
206
+ this.types[name] = { name, type: type };
207
+ }
208
+ }
209
+ catch (error) {
210
+ notifyMetadataTypeError(`Failed to load types.json: ${error}`);
211
+ }
212
+ void this.updateProperties();
213
+ });
214
+ return this.typesWriter;
215
+ }
175
216
  toJSON() {
176
217
  const types = {};
177
218
  Object.values(this.types).forEach((t) => {
@@ -182,6 +223,8 @@ export class MetadataTypeManager {
182
223
  };
183
224
  }
184
225
  setType(field, type) {
226
+ if (this.app.vault.supportsConditionalTextWrites)
227
+ this.getTypesWriter().assertEditable();
185
228
  this.types[field] = { name: field, type };
186
229
  this.properties[field] ||= {
187
230
  name: field,
@@ -0,0 +1,48 @@
1
+ import type { App } from "./context.svelte";
2
+ import type { IndexProjectionRegistration } from "./storage/index-projection";
3
+ import type { ContributionStatus, IndexingCommand, IndexingStatus, IndexingValue, SearchIndexingSelection } from "./native-indexing-protocol";
4
+ export type * from "./native-indexing-protocol";
5
+ /** An explicitly supplied, already admitted native vault transport. */
6
+ export interface NativeBackgroundIndexing {
7
+ call(command: IndexingCommand): Promise<IndexingValue>;
8
+ }
9
+ export interface NativeIndexingProgressPresentation {
10
+ title: string;
11
+ source: string;
12
+ readyMessage: string;
13
+ }
14
+ /** One App-owned observation boundary; native work outlives renderer detach. */
15
+ export interface NativeBackgroundIndexingObserver {
16
+ readonly detached: boolean;
17
+ selectSearch(selection: SearchIndexingSelection): Promise<ContributionStatus>;
18
+ disableSearch(): Promise<void>;
19
+ getStatus(): Promise<IndexingStatus | null>;
20
+ refresh(rebuild: boolean, presentation?: NativeIndexingProgressPresentation): Promise<IndexingStatus>;
21
+ }
22
+ /** App-owned observer and declaration adapter; never an indexing writer. */
23
+ export declare class NativeBackgroundIndexingSession implements NativeBackgroundIndexingObserver {
24
+ private readonly app;
25
+ private readonly port;
26
+ private isDetached;
27
+ private searchDeclared;
28
+ private searchVersion;
29
+ private searchControl;
30
+ private readonly pending;
31
+ private readonly contributions;
32
+ private readonly observers;
33
+ constructor(app: App, port: NativeBackgroundIndexing);
34
+ get detached(): boolean;
35
+ selectSearch(selection: SearchIndexingSelection): Promise<ContributionStatus>;
36
+ disableSearch(): Promise<void>;
37
+ getStatus(): Promise<IndexingStatus | null>;
38
+ registerProcessor(owner: string, extension: string): void;
39
+ registerProjection(owner: string, id: string, registration: IndexProjectionRegistration): void;
40
+ private register;
41
+ private callSearch;
42
+ ready(owner?: string): Promise<void>;
43
+ disable(owner: string): Promise<void>;
44
+ getContributionStatuses(): ContributionStatus[];
45
+ load(): Promise<void>;
46
+ refresh(rebuild: boolean, presentation?: NativeIndexingProgressPresentation): Promise<IndexingStatus>;
47
+ dispose(): void;
48
+ }
@@ -0,0 +1,208 @@
1
+ /** App-owned observer and declaration adapter; never an indexing writer. */
2
+ export class NativeBackgroundIndexingSession {
3
+ app;
4
+ port;
5
+ isDetached = false;
6
+ searchDeclared = false;
7
+ searchVersion = 0;
8
+ searchControl = Promise.resolve();
9
+ pending = new Map();
10
+ contributions = new Map();
11
+ observers = new Set();
12
+ constructor(app, port) {
13
+ this.app = app;
14
+ this.port = port;
15
+ }
16
+ get detached() {
17
+ return this.isDetached;
18
+ }
19
+ async selectSearch(selection) {
20
+ if (this.detached)
21
+ throw new Error("Native indexing observer is detached");
22
+ this.searchVersion += 1;
23
+ this.searchDeclared = true;
24
+ const contribution = await this.register("search", {
25
+ kind: "select_search",
26
+ selection,
27
+ });
28
+ if (!contribution || contribution.projection_id !== "search")
29
+ throw new Error("Native Search selection is unavailable");
30
+ return contribution;
31
+ }
32
+ async disableSearch() {
33
+ if (this.detached)
34
+ return;
35
+ // Controls share one order even when selection's response is uncertain.
36
+ const version = ++this.searchVersion;
37
+ await this.callSearch({ kind: "disable_search" });
38
+ if (version !== this.searchVersion)
39
+ return;
40
+ this.searchDeclared = false;
41
+ this.contributions.delete("search");
42
+ this.pending.delete("search");
43
+ }
44
+ async getStatus() {
45
+ return this.detached
46
+ ? null
47
+ : (await this.port.call({ kind: "status" })).status;
48
+ }
49
+ registerProcessor(owner, extension) {
50
+ this.register(owner, {
51
+ kind: "register_processor",
52
+ owner_plugin_id: owner,
53
+ extension,
54
+ });
55
+ }
56
+ registerProjection(owner, id, registration) {
57
+ this.register(owner, {
58
+ kind: "register_projection",
59
+ contribution: {
60
+ projection_id: id,
61
+ owner_plugin_id: owner,
62
+ schema_version: registration.version,
63
+ configuration_hash: registration.configurationHash ?? "",
64
+ path_prefix: registration.source?.pathPrefix ?? null,
65
+ },
66
+ });
67
+ }
68
+ register(owner, command) {
69
+ const request = owner === "search" ? this.callSearch(command) : this.port.call(command);
70
+ const operation = request.then(({ contribution }) => {
71
+ if (!contribution || this.detached)
72
+ return contribution;
73
+ this.contributions.set(contribution.projection_id, {
74
+ owner,
75
+ status: contribution,
76
+ });
77
+ if (contribution.placement === "client_dependent") {
78
+ this.app.notifications.notify({
79
+ title: "Indexing unavailable",
80
+ source: owner,
81
+ severity: "warning",
82
+ message: `${contribution.projection_id}: ${contribution.message ?? "Requires a client processor"}`,
83
+ });
84
+ }
85
+ return contribution;
86
+ });
87
+ this.pending.set(owner, [...(this.pending.get(owner) ?? []), operation]);
88
+ // Activation/load observes failures; do not create an unhandled rejection
89
+ // while a synchronous plugin declaration is still returning its query handle.
90
+ void operation.catch(() => { });
91
+ return operation;
92
+ }
93
+ callSearch(command) {
94
+ const operation = this.searchControl.then(() => {
95
+ if (this.detached)
96
+ throw new Error("Native indexing observer is detached");
97
+ return this.port.call(command);
98
+ });
99
+ this.searchControl = operation.then(() => undefined, () => undefined);
100
+ return operation;
101
+ }
102
+ async ready(owner) {
103
+ const operations = owner
104
+ ? (this.pending.get(owner) ?? [])
105
+ : [...this.pending.values()].flat();
106
+ await Promise.all(operations);
107
+ }
108
+ async disable(owner) {
109
+ if (this.detached)
110
+ return;
111
+ if (owner === "search" && this.searchDeclared)
112
+ await this.disableSearch();
113
+ await this.ready(owner);
114
+ for (const [id, contribution] of this.contributions) {
115
+ if (contribution.owner !== owner)
116
+ continue;
117
+ if (id === "search") {
118
+ await this.disableSearch();
119
+ }
120
+ else if (contribution.status.placement === "native" &&
121
+ !id.startsWith("processor:")) {
122
+ await this.port.call({ kind: "disable_projection", projection_id: id });
123
+ }
124
+ this.contributions.delete(id);
125
+ }
126
+ this.pending.delete(owner);
127
+ }
128
+ getContributionStatuses() {
129
+ return [...this.contributions.values()].map(({ status }) => ({
130
+ ...status,
131
+ }));
132
+ }
133
+ async load() {
134
+ const selected = await this.port.call({ kind: "select_metadata" });
135
+ if (!selected.status)
136
+ throw new Error("Native metadata owner was not selected");
137
+ await this.ready();
138
+ // Search declares after metadata-loaded. Its owning plugin can already be
139
+ // enabled; a replacement App without that plugin must retire the old owner.
140
+ if (!this.searchDeclared &&
141
+ !this.app.plugins.plugins.get("search")?.enabled)
142
+ await this.disableSearch();
143
+ await this.ready();
144
+ await this.refresh(false);
145
+ }
146
+ async refresh(rebuild, presentation) {
147
+ if (this.detached)
148
+ throw new Error("Native indexing observer is detached");
149
+ return this.app.notifications.withProgress({
150
+ title: presentation?.title ??
151
+ (rebuild ? "Rebuilding metadata cache" : "Loading metadata cache"),
152
+ source: presentation?.source ?? "Metadata",
153
+ location: "status",
154
+ persistOnError: true,
155
+ }, async (progress) => {
156
+ let observing = false;
157
+ const report = (status) => {
158
+ if (!status || this.detached)
159
+ return;
160
+ progress.report({
161
+ current: status.processed,
162
+ total: status.total,
163
+ message: status.error ??
164
+ (status.active
165
+ ? `Indexing ${status.phase}`
166
+ : status.pending > 0
167
+ ? "Indexing pending changes"
168
+ : (presentation?.readyMessage ?? "Metadata index ready")),
169
+ });
170
+ };
171
+ const observe = async () => {
172
+ if (observing || this.detached)
173
+ return;
174
+ observing = true;
175
+ try {
176
+ report((await this.port.call({ kind: "status" })).status);
177
+ }
178
+ catch {
179
+ /* The accepted refresh owns its definitive outcome. */
180
+ }
181
+ finally {
182
+ observing = false;
183
+ }
184
+ };
185
+ const timer = setInterval(() => void observe(), 100);
186
+ this.observers.add(timer);
187
+ try {
188
+ const result = await this.port.call({ kind: "refresh", rebuild });
189
+ if (!result.status)
190
+ throw new Error("Native metadata owner is unavailable");
191
+ report(result.status);
192
+ if (result.status.error)
193
+ throw new Error(result.status.error);
194
+ return result.status;
195
+ }
196
+ finally {
197
+ clearInterval(timer);
198
+ this.observers.delete(timer);
199
+ }
200
+ });
201
+ }
202
+ dispose() {
203
+ this.isDetached = true;
204
+ for (const timer of this.observers)
205
+ clearInterval(timer);
206
+ this.observers.clear();
207
+ }
208
+ }