@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
@@ -1,6 +1,8 @@
1
+ import { rebindSearchDocument } from "./search-embedding-record";
1
2
  import { collectSearchQueryTerms } from "../search-query";
2
3
  import { buildSearchResult, compareSearchResults, embeddingErrorState, EMPTY_APP_DATABASE_CAPABILITIES, hasSearchPropertyNames, MIN_VECTOR_SEARCH_SCORE, normalizeSearchDocument, pathWithinPrefix, rankSearchScores, resolveSearchQueryEnhancementDiagnostics, scoreSearchDocument, scoreVectorDocument, searchDocumentProperties, searchPropertyNames, } from "./app-database";
3
4
  import { assertProjectionReadAccess, assertProjectionWriteAccess, compileProjectionQuerySql, evaluateProjectionQuery, indexedValuesForRow, MAX_PROJECTION_ROWS_PER_SOURCE, projectionIndexStatus, PUBLIC_TASKS_PROJECTION_ID, } from "./index-projection";
5
+ import { projectionQueryCandidates } from "./projection-query-candidates";
4
6
  import { TASK_PROJECTION_FIELDS, TASK_PROJECTION_VERSION, taskQueryToIndexQuery, } from "./task-projection";
5
7
  import { createSearchEmbeddingProvider, } from "./search-embedding-provider";
6
8
  import { isStructuredSearchQuery } from "./search-query-evaluator";
@@ -580,6 +582,7 @@ export class TursoAppDatabase {
580
582
  kind;
581
583
  connection = null;
582
584
  opened = false;
585
+ closeTask = null;
583
586
  nativeFullTextSearch = false;
584
587
  nativeVectorSearch = false;
585
588
  searchEmbeddingProviderConfig = null;
@@ -669,33 +672,65 @@ export class TursoAppDatabase {
669
672
  }
670
673
  }
671
674
  async close() {
672
- if (!this.connection)
673
- return;
675
+ if (this.closeTask)
676
+ return this.closeTask;
674
677
  const connection = this.connection;
675
- this.connection = null;
676
- this.opened = false;
677
- await this.searchEmbeddingProvider?.dispose?.();
678
- this.searchEmbeddingProvider = null;
679
- this.changeListeners.clear();
680
- await connection.close();
678
+ if (!connection)
679
+ return;
680
+ this.closeTask = (async () => {
681
+ await this.commitQueue;
682
+ await this.searchEmbeddingProvider?.dispose?.();
683
+ this.searchEmbeddingProvider = null;
684
+ await connection.close();
685
+ this.connection = null;
686
+ this.opened = false;
687
+ this.changeListeners.clear();
688
+ })();
689
+ try {
690
+ await this.closeTask;
691
+ }
692
+ finally {
693
+ this.closeTask = null;
694
+ }
681
695
  }
682
696
  async beginSearchIndexingBatch() { }
683
697
  async endSearchIndexingBatch() { }
684
698
  async configureSearchEmbeddingProvider(provider) {
699
+ const pending = this.commitQueue.then(() => this.configureSearchProviderImmediately(provider));
700
+ this.commitQueue = pending.then(() => undefined, () => undefined);
701
+ return pending;
702
+ }
703
+ async configureSearchProviderImmediately(provider) {
685
704
  if (JSON.stringify(provider) ===
686
705
  JSON.stringify(this.searchEmbeddingProviderConfig))
687
706
  return;
688
707
  const previous = this.searchEmbeddingProvider;
689
- this.searchEmbeddingProviderConfig = clone(provider);
690
- this.searchEmbeddingProvider = createSearchEmbeddingProvider(provider);
691
- await previous?.dispose?.();
692
- await this.commit([
693
- {
694
- sql: `INSERT INTO app_meta (key, value_json) VALUES (?, ?)
708
+ const next = createSearchEmbeddingProvider(provider);
709
+ try {
710
+ const statements = [
711
+ {
712
+ sql: `INSERT INTO app_meta (key, value_json) VALUES (?, ?)
695
713
  ON CONFLICT(key) DO UPDATE SET value_json = excluded.value_json`,
696
- args: [SEARCH_EMBEDDING_PROVIDER_META_KEY, JSON.stringify(provider)],
697
- },
698
- ], ["search"]);
714
+ args: [SEARCH_EMBEDDING_PROVIDER_META_KEY, JSON.stringify(provider)],
715
+ },
716
+ ];
717
+ if (next)
718
+ for (const document of await this.listSearchDocuments()) {
719
+ statements.push(...this.searchDocumentStatements(rebindSearchDocument(document, next.config)));
720
+ }
721
+ await this.commitImmediately(statements, ["search"], [], undefined, () => {
722
+ this.searchEmbeddingProviderConfig = clone(provider);
723
+ this.searchEmbeddingProvider = next;
724
+ });
725
+ }
726
+ catch (error) {
727
+ if (this.searchEmbeddingProvider === next)
728
+ await previous?.dispose?.();
729
+ else
730
+ await next?.dispose?.();
731
+ throw error;
732
+ }
733
+ await previous?.dispose?.();
699
734
  }
700
735
  async getSearchEmbeddingProvider() {
701
736
  return clone(this.searchEmbeddingProviderConfig);
@@ -1207,7 +1242,7 @@ export class TursoAppDatabase {
1207
1242
  ...input.patch,
1208
1243
  ownerId: input.patch.status === "running" ? existing.ownerId : undefined,
1209
1244
  leaseUntil: input.patch.status === "running"
1210
- ? input.patch.leaseUntil ?? existing.leaseUntil
1245
+ ? (input.patch.leaseUntil ?? existing.leaseUntil)
1211
1246
  : undefined,
1212
1247
  };
1213
1248
  await this.commit([
@@ -1561,7 +1596,7 @@ export class TursoAppDatabase {
1561
1596
  Boolean(options.sourceProviderIds?.length) ||
1562
1597
  Boolean(options.pathPrefix);
1563
1598
  if (allPathsRequired) {
1564
- return this.evaluateSearchDocumentsForPaths(query, options, await this.queryAllSearchPaths(options.limit, options));
1599
+ return this.evaluateSearchDocumentsForPaths(query, options, await this.queryAllSearchPaths(options));
1565
1600
  }
1566
1601
  const candidates = new Set();
1567
1602
  const includesLexical = requestedMode !== "vector";
@@ -1588,7 +1623,7 @@ export class TursoAppDatabase {
1588
1623
  async searchDocumentPaths(query, options = {}) {
1589
1624
  return (await this.searchDocuments(query, options)).map((result) => result.document.path);
1590
1625
  }
1591
- async queryAllSearchPaths(limit = 100, options = {}) {
1626
+ async queryAllSearchPaths(options = {}) {
1592
1627
  const clauses = [];
1593
1628
  const args = [];
1594
1629
  if (options.sourceProviderIds?.length) {
@@ -1601,13 +1636,13 @@ export class TursoAppDatabase {
1601
1636
  }
1602
1637
  const rows = await this.requireConnection().all(`SELECT path FROM search_docs
1603
1638
  ${clauses.length > 0 ? `WHERE ${clauses.join(" AND ")}` : ""}
1604
- ORDER BY path LIMIT ?`, ...args, Math.max(limit, 100));
1639
+ ORDER BY path`, ...args);
1605
1640
  return rows.map((row) => row.path);
1606
1641
  }
1607
1642
  async queryLexicalPaths(query, limit = 100) {
1608
1643
  const terms = collectSearchQueryTerms(query);
1609
1644
  if (!terms.length || !this.nativeFullTextSearch) {
1610
- return this.queryAllSearchPaths(limit);
1645
+ return this.queryAllSearchPaths();
1611
1646
  }
1612
1647
  const ftsQuery = ftsQueryForTerms(terms);
1613
1648
  const rows = await this.requireConnection().all(`SELECT path,
@@ -1857,11 +1892,7 @@ export class TursoAppDatabase {
1857
1892
  async queryProjection(projectionId, query = {}, readerPluginId) {
1858
1893
  const definition = await this.getProjectionDefinition(projectionId);
1859
1894
  assertProjectionReadAccess(definition, readerPluginId);
1860
- const compiled = compileProjectionQuerySql(projectionId, {
1861
- ...query,
1862
- after: undefined,
1863
- limit: query.after ? undefined : query.limit,
1864
- }, definition);
1895
+ const compiled = compileProjectionQuerySql(projectionId, projectionQueryCandidates(query), definition);
1865
1896
  const [rows, sources] = await Promise.all([
1866
1897
  this.requireConnection().all(compiled.sql, ...compiled.args),
1867
1898
  this.getProjectionSources(projectionId),
@@ -1893,8 +1924,13 @@ export class TursoAppDatabase {
1893
1924
  const definition = await this.getProjectionDefinition(query.projectionId);
1894
1925
  assertProjectionReadAccess(definition, readerPluginId);
1895
1926
  const edges = await this.requireConnection().all(query.direction === "in"
1896
- ? `SELECT * FROM index_projection_edges WHERE projection_id = ? AND relation = ? AND target_row_id = ? ORDER BY ordinal LIMIT ?`
1897
- : `SELECT * FROM index_projection_edges WHERE projection_id = ? AND relation = ? AND source_row_id = ? ORDER BY ordinal LIMIT ?`, query.projectionId, query.relation, query.rowId, query.limit ?? 1000);
1927
+ ? `SELECT * FROM index_projection_edges
1928
+ WHERE projection_id = ? AND relation = ?
1929
+ AND (target_row_id = ? OR target_path = (
1930
+ SELECT source_path FROM index_projection_rows
1931
+ WHERE projection_id = ? AND row_id = ?
1932
+ )) ORDER BY ordinal`
1933
+ : `SELECT * FROM index_projection_edges WHERE projection_id = ? AND relation = ? AND source_row_id = ? ORDER BY ordinal`, query.projectionId, query.relation, query.rowId, ...(query.direction === "in" ? [query.projectionId, query.rowId] : []));
1898
1934
  const rows = [];
1899
1935
  for (const edge of edges) {
1900
1936
  const targetId = query.direction === "in"
@@ -1933,7 +1969,7 @@ export class TursoAppDatabase {
1933
1969
  this.commitQueue = committed.then(() => undefined, () => undefined);
1934
1970
  return committed;
1935
1971
  }
1936
- async commitImmediately(statements, domains, paths = [], renamed) {
1972
+ async commitImmediately(statements, domains, paths = [], renamed, onCommitted) {
1937
1973
  const revision = this.currentRevision + 1;
1938
1974
  const change = {
1939
1975
  revision,
@@ -1963,6 +1999,7 @@ export class TursoAppDatabase {
1963
1999
  },
1964
2000
  ], "immediate");
1965
2001
  this.currentRevision = revision;
2002
+ onCommitted?.();
1966
2003
  for (const listener of this.changeListeners)
1967
2004
  listener(clone(change));
1968
2005
  return change;
@@ -0,0 +1,3 @@
1
+ export declare const DEFAULT_VAULT_EXCLUDES: readonly string[];
2
+ /** Discovery projection only; explicit resources and adapter IO stay unchanged. */
3
+ export declare function createVaultListingFilter(readPatterns: () => readonly string[]): () => (path: string) => boolean;
@@ -0,0 +1,36 @@
1
+ import { compileEditorAssociationGlob } from "../glob";
2
+ import { normalizePath } from "./path";
3
+ export const DEFAULT_VAULT_EXCLUDES = Object.freeze([
4
+ ".git",
5
+ ".jj",
6
+ ]);
7
+ /** Discovery projection only; explicit resources and adapter IO stay unchanged. */
8
+ export function createVaultListingFilter(readPatterns) {
9
+ let signature;
10
+ let matchers = [];
11
+ return () => {
12
+ const value = readPatterns();
13
+ const patterns = Array.isArray(value) ? value : DEFAULT_VAULT_EXCLUDES;
14
+ const next = JSON.stringify(patterns);
15
+ if (signature !== next) {
16
+ matchers = patterns
17
+ .filter((pattern) => typeof pattern === "string")
18
+ .map((pattern) => compileEditorAssociationGlob(pattern, { caseSensitive: true }))
19
+ .filter((matcher) => matcher !== null);
20
+ signature = next;
21
+ }
22
+ const current = matchers;
23
+ return (path) => {
24
+ const relative = normalizePath(path).replace(/^\/+|\/+$/gu, "");
25
+ if (!relative)
26
+ return true;
27
+ const parts = relative.split("/");
28
+ for (let length = 1; length <= parts.length; length++) {
29
+ const ancestor = parts.slice(0, length).join("/");
30
+ if (current.some((matcher) => matcher.match(ancestor)))
31
+ return false;
32
+ }
33
+ return true;
34
+ };
35
+ };
36
+ }
@@ -0,0 +1,19 @@
1
+ import { type ChangeEvent, type DataAdapter, type Stat, type TAbstractFile } from "./fs";
2
+ import type { Vault } from "./vault.svelte";
3
+ type Snapshot = Map<string, Stat>;
4
+ export type VaultTreeEvent = {
5
+ type: "create" | "modify" | "delete" | "rename";
6
+ file: TAbstractFile;
7
+ oldPath?: string;
8
+ };
9
+ /** Scan privately; a failed or retired scan cannot mutate the live tree. */
10
+ export declare function scanVaultTree(adapter: DataAdapter, current: () => boolean, bounds?: {
11
+ entries: number;
12
+ bytes: number;
13
+ }): Promise<Snapshot | null>;
14
+ /** Prepare all identities/events before the synchronous tree commit. */
15
+ export declare function projectVaultTree(vault: Vault, previous: Record<string, TAbstractFile>, snapshot: Snapshot, hints: readonly ChangeEvent[]): {
16
+ files: Record<string, TAbstractFile>;
17
+ events: VaultTreeEvent[];
18
+ };
19
+ export {};
@@ -0,0 +1,137 @@
1
+ import { TFile, TFolder, } from "./fs";
2
+ import { dirname } from "./path";
3
+ const limits = { entries: 100_000, bytes: 16 * 1024 * 1024 };
4
+ /** Scan privately; a failed or retired scan cannot mutate the live tree. */
5
+ export async function scanVaultTree(adapter, current, bounds = limits) {
6
+ const snapshot = new Map([
7
+ ["/", { type: "folder", ctime: 0, mtime: 0, size: 0 }],
8
+ ]);
9
+ const folders = ["/"];
10
+ let bytes = 96;
11
+ const encoder = new TextEncoder();
12
+ for (let index = 0; index < folders.length; index++) {
13
+ if (!current())
14
+ return null;
15
+ const parent = folders[index];
16
+ const listed = await adapter.list(parent);
17
+ for (const [type, names] of [
18
+ ["file", listed.files],
19
+ ["folder", listed.folders],
20
+ ]) {
21
+ for (const name of names) {
22
+ if (!current())
23
+ return null;
24
+ if (!name || name === "." || name === ".." || /[/\\\0]/u.test(name))
25
+ throw new Error("Invalid vault snapshot child path");
26
+ const path = parent === "/" ? name : `${parent}/${name}`;
27
+ bytes += encoder.encode(path).byteLength + 96;
28
+ if (snapshot.has(path) ||
29
+ snapshot.size >= bounds.entries ||
30
+ bytes > bounds.bytes)
31
+ throw new Error("Vault reconciliation snapshot exceeds its bounds or duplicates a path");
32
+ const stat = await adapter.stat(path);
33
+ if (!stat ||
34
+ stat.type !== type ||
35
+ ![stat.ctime, stat.mtime, stat.size].every(Number.isFinite) ||
36
+ stat.size < 0)
37
+ throw new Error(`Incomplete vault reconciliation snapshot: ${path}`);
38
+ snapshot.set(path, { ...stat });
39
+ if (type === "folder")
40
+ folders.push(path);
41
+ }
42
+ }
43
+ }
44
+ return current() ? snapshot : null;
45
+ }
46
+ function validPath(path) {
47
+ return (!!path &&
48
+ !/[\\\0]/u.test(path) &&
49
+ path.split("/").every((part) => !!part && part !== "." && part !== ".."));
50
+ }
51
+ function depth(path) {
52
+ return path.split("/").length;
53
+ }
54
+ function sameType(file, stat) {
55
+ return stat.type === "file" ? file instanceof TFile : file instanceof TFolder;
56
+ }
57
+ /** Prepare all identities/events before the synchronous tree commit. */
58
+ export function projectVaultTree(vault, previous, snapshot, hints) {
59
+ const renamed = new Map();
60
+ const forced = new Set();
61
+ for (const hint of hints) {
62
+ if (!validPath(hint.path) ||
63
+ (hint.previousPath !== undefined && !validPath(hint.previousPath)))
64
+ throw new Error("Invalid vault reconciliation hint");
65
+ if (hint.type === "modify")
66
+ forced.add(hint.path);
67
+ const old = hint.previousPath;
68
+ if (!old || old === hint.path || snapshot.has(old) || previous[hint.path])
69
+ continue;
70
+ const source = previous[old], target = snapshot.get(hint.path);
71
+ if (!source || !target || !sameType(source, target))
72
+ continue;
73
+ for (const path of Object.keys(previous)) {
74
+ if (path !== old &&
75
+ !(source instanceof TFolder && path.startsWith(`${old}/`)))
76
+ continue;
77
+ const destination = hint.path + path.slice(old.length);
78
+ const stat = snapshot.get(destination);
79
+ if (stat &&
80
+ !previous[destination] &&
81
+ !snapshot.has(path) &&
82
+ sameType(previous[path], stat)) {
83
+ const existing = renamed.get(destination);
84
+ if (existing && existing !== path)
85
+ throw new Error("Ambiguous vault rename hints");
86
+ renamed.set(destination, path);
87
+ }
88
+ }
89
+ }
90
+ const files = Object.create(null);
91
+ const events = [];
92
+ const consumed = new Set();
93
+ const paths = [...snapshot.keys()].sort((a, b) => depth(a) - depth(b) || a.localeCompare(b));
94
+ for (const path of paths) {
95
+ const stat = snapshot.get(path);
96
+ const oldPath = renamed.get(path) ?? path;
97
+ const existing = previous[oldPath];
98
+ const file = sameType(existing, stat)
99
+ ? existing
100
+ : stat.type === "file"
101
+ ? new TFile(path, { ...stat }, null, vault)
102
+ : new TFolder(path, [], null, vault);
103
+ if (consumed.has(file))
104
+ throw new Error("Duplicate vault resource identity");
105
+ consumed.add(file);
106
+ files[path] = file;
107
+ if (path === "/")
108
+ continue;
109
+ if (file !== existing)
110
+ events.push({ type: "create", file });
111
+ else if (oldPath !== path)
112
+ events.push({ type: "rename", file, oldPath });
113
+ else if (file instanceof TFile &&
114
+ (forced.has(path) ||
115
+ file.stat.mtime !== stat.mtime ||
116
+ file.stat.ctime !== stat.ctime ||
117
+ file.stat.size !== stat.size))
118
+ events.push({ type: "modify", file });
119
+ }
120
+ const deleted = Object.values(previous).filter((file) => !consumed.has(file));
121
+ deleted.sort((a, b) => depth(b.path) - depth(a.path) || a.path.localeCompare(b.path));
122
+ events.unshift(...deleted.map((file) => ({ type: "delete", file })));
123
+ // Nothing above mutates canonical nodes. Commit after all validation succeeds.
124
+ for (const path of paths) {
125
+ const file = files[path];
126
+ file.path = path === "/" ? "" : path;
127
+ file.parent = path === "/" ? null : files[dirname(path)];
128
+ if (file instanceof TFile)
129
+ Object.assign(file.stat, snapshot.get(path));
130
+ else if (file instanceof TFolder)
131
+ file.children.splice(0);
132
+ }
133
+ for (const path of paths)
134
+ if (path !== "/")
135
+ files[path].parent.children.push(files[path]);
136
+ return { files, events };
137
+ }
@@ -1,4 +1,8 @@
1
+ import type { NativeTasks } from "../native-tasks";
1
2
  import { type AppDatabase, type AppDatabaseCapabilities, type AppDatabaseDescriptor, type AppDatabaseProvider, type RuntimeTarget } from "./app-database";
3
+ import type { NativeBackgroundIndexing } from "../native-background-indexing";
4
+ import type { NativeHistoryIndex } from "../native-history-index";
5
+ import type { NativeEditingIntents } from "../native-editing-intents";
2
6
  import type { VaultAdapter } from "./fs";
3
7
  import { type VaultProfile } from "./vault-state";
4
8
  export type VaultSessionAppDatabaseStatus = "ready" | "blocked";
@@ -22,6 +26,10 @@ export interface VaultSession {
22
26
  profile?: VaultProfile;
23
27
  vaultAdapter: VaultAdapter;
24
28
  appDatabase?: AppDatabase;
29
+ backgroundIndexing?: NativeBackgroundIndexing;
30
+ nativeHistoryIndex?: NativeHistoryIndex;
31
+ nativeEditingIntents?: NativeEditingIntents;
32
+ nativeTasks?: NativeTasks;
25
33
  appDatabaseState: VaultSessionAppDatabaseState;
26
34
  awaitAppDatabase?: (options?: {
27
35
  signal?: AbortSignal;
@@ -128,6 +128,12 @@ async function createPreferredAppDatabase(vaultId, runtime, provider) {
128
128
  await database.open();
129
129
  return { appDatabase: database, state: readyState(database, false) };
130
130
  }
131
+ if (runtime === "tauri-desktop") {
132
+ if (!provider)
133
+ throw new Error("tauri-desktop requires an AppDatabaseProvider");
134
+ const database = await provider.open({ vaultId, runtime, role: "direct" });
135
+ return { appDatabase: database, state: readyState(database, false) };
136
+ }
131
137
  if (runtime === "deno-desktop") {
132
138
  const database = provider
133
139
  ? await provider.open({ vaultId, runtime, role: "direct" })
@@ -138,9 +144,11 @@ async function createPreferredAppDatabase(vaultId, runtime, provider) {
138
144
  });
139
145
  return { appDatabase: database, state: readyState(database, false) };
140
146
  }
141
- if (runtime === "deno-server") {
147
+ if (runtime === "node-server" ||
148
+ runtime === "deno-server" ||
149
+ runtime === "rust-server") {
142
150
  if (!provider) {
143
- throw new Error("deno-server requires an AppDatabaseProvider");
151
+ throw new Error("server-hosted vaults require an AppDatabaseProvider");
144
152
  }
145
153
  const database = await provider.open({
146
154
  vaultId,
@@ -1,9 +1,11 @@
1
+ import type { FileEditingSessionRegistry } from "../file-editing-sessions";
1
2
  import { EventDispatcher } from "../events";
2
3
  import {
3
4
  FileCache,
4
5
  TFile,
5
6
  TFolder,
6
7
  type DataAdapter,
8
+ type ChangeEvent,
7
9
  type DataWriteOptions,
8
10
  type TAbstractFile,
9
11
  } from "./fs";
@@ -19,6 +21,9 @@ export declare const ENOTDIR: {
19
21
  prepareStackTrace(err: Error, stackTraces: NodeJS.CallSite[]): any;
20
22
  stackTraceLimit: number;
21
23
  };
24
+ export interface VaultListingOptions {
25
+ listingExclusions?: () => readonly string[];
26
+ }
22
27
  export interface VaultGlobOptions {
23
28
  caseSensitive?: boolean;
24
29
  }
@@ -42,9 +47,18 @@ export declare class Vault extends EventDispatcher<{
42
47
  readonly adapter: DataAdapter;
43
48
  private files;
44
49
  private filesByName;
50
+ private mutationTail;
51
+ private mutate;
52
+ private mutateFile;
53
+ private reconcileNow;
45
54
  configDir: string;
46
55
  readonly cache: FileCache;
47
- constructor(adapter: DataAdapter);
56
+ private readonly listingFilter;
57
+ constructor(
58
+ adapter: DataAdapter,
59
+ operations?: Pick<FileEditingSessionRegistry, "track">,
60
+ options?: VaultListingOptions,
61
+ );
48
62
  private getPath;
49
63
  getName(): string;
50
64
  exists(path: string, sensitive?: boolean): Promise<boolean>;
@@ -52,6 +66,23 @@ export declare class Vault extends EventDispatcher<{
52
66
  list(path?: string): Promise<import("./fs").ListedFiles>;
53
67
  load(): Promise<void>;
54
68
  reload(): Promise<TFolder>;
69
+ reconcile(
70
+ hints?: readonly ChangeEvent[],
71
+ current?: () => boolean,
72
+ context?: Record<string, unknown>,
73
+ ): Promise<void>;
74
+ private deleteNow;
75
+ private trashNow;
76
+ private atomicWriteNow;
77
+ private renameNow;
78
+ private modifyNow;
79
+ private modifyIfUnchangedNow;
80
+ private modifyBinaryNow;
81
+ private processNow;
82
+ private appendNow;
83
+ private appendBinaryNow;
84
+ private copyNow;
85
+ private recordFile;
55
86
  private addToFileNameIndex;
56
87
  private removeFromFileNameIndex;
57
88
  private rebuildFileNameIndex;
@@ -72,6 +103,12 @@ export declare class Vault extends EventDispatcher<{
72
103
  data: string,
73
104
  options?: DataWriteOptions,
74
105
  ): Promise<TFile>;
106
+ get supportsExclusiveCreate(): boolean;
107
+ createExclusive(
108
+ path: string,
109
+ data: string | ArrayBuffer,
110
+ options?: DataWriteOptions,
111
+ ): Promise<TFile>;
75
112
  /**
76
113
  * Create a new binary file inside the vault.
77
114
  *
@@ -103,9 +140,13 @@ export declare class Vault extends EventDispatcher<{
103
140
  * children
104
141
  * @public
105
142
  */
106
- delete(file: TAbstractFile, force?: boolean, context?: {
107
- operationOrigin?: string;
108
- }): Promise<void>;
143
+ delete(
144
+ file: TAbstractFile,
145
+ force?: boolean,
146
+ context?: {
147
+ operationOrigin?: string;
148
+ },
149
+ ): Promise<void>;
109
150
  trash(file: TAbstractFile, system?: boolean): Promise<void>;
110
151
  /**
111
152
  * Read a plaintext file that is stored inside the vault, directly from disk.
@@ -147,6 +188,13 @@ export declare class Vault extends EventDispatcher<{
147
188
  * @public
148
189
  */
149
190
  modify(file: TFile, data: string, options?: DataWriteOptions): Promise<void>;
191
+ get supportsConditionalTextWrites(): boolean;
192
+ modifyIfUnchanged(
193
+ file: TFile,
194
+ data: string,
195
+ expected: string | null,
196
+ options?: DataWriteOptions,
197
+ ): Promise<void>;
150
198
  /**
151
199
  * Modify the contents of a binary file.
152
200
  *
@@ -241,6 +289,7 @@ export declare class Vault extends EventDispatcher<{
241
289
  getFiles(): TFile[];
242
290
  /**
243
291
  * Iterate loaded files without allocating a second vault-sized collection.
292
+ *
244
293
  * @public
245
294
  */
246
295
  iterateFiles(): IterableIterator<TFile>;