@lapis-notes/api 0.1.0 → 0.1.3

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 (109) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +5 -0
  3. package/dist/app-database-host.d.ts +1 -0
  4. package/dist/app-database-host.js +1 -0
  5. package/dist/browser-sync-security.d.ts +45 -0
  6. package/dist/browser-sync-security.js +189 -0
  7. package/dist/build-tools/vite.d.ts +15 -0
  8. package/dist/build-tools/vite.js +36 -0
  9. package/dist/cache.svelte.d.ts +1 -1
  10. package/dist/cache.svelte.js +4 -1
  11. package/dist/components/editor/editor.css +54 -2
  12. package/dist/components/editor/extensions/lint/lapis-lint-tooltip.svelte +18 -5
  13. package/dist/components/editor/extensions/lint/lint-tooltip-actions.d.ts +0 -2
  14. package/dist/components/editor/extensions/lint/lint-tooltip-actions.js +0 -14
  15. package/dist/components/editor/language-service/index.js +23 -2
  16. package/dist/components/icon/icon-list.svelte.d.ts +1 -1
  17. package/dist/components/select/select.svelte.d.ts +1 -1
  18. package/dist/context.svelte.d.ts +11 -1
  19. package/dist/context.svelte.js +38 -1
  20. package/dist/docs-api.d.ts +2 -0
  21. package/dist/docs-api.js +1 -0
  22. package/dist/generated/plugin-host-modules.schema.generated.d.ts +443 -13
  23. package/dist/generated/plugin-host-modules.schema.generated.js +970 -306
  24. package/dist/index.d.ts +5 -0
  25. package/dist/index.js +5 -0
  26. package/dist/language-service/manager.d.ts +5 -0
  27. package/dist/language-service/manager.js +58 -14
  28. package/dist/lapis-extension.d.ts +1 -1
  29. package/dist/lapis-extension.js +12 -0
  30. package/dist/nostr-signer.d.ts +68 -0
  31. package/dist/nostr-signer.js +49 -0
  32. package/dist/plugin-asset-server.d.ts +1 -0
  33. package/dist/plugin-asset-server.js +3 -0
  34. package/dist/plugin-capability-facade.d.ts +1 -1
  35. package/dist/plugin-capability-facade.js +6 -0
  36. package/dist/plugin-distribution/catalog-media.d.ts +4 -0
  37. package/dist/plugin-distribution/catalog-media.js +40 -0
  38. package/dist/plugin-distribution/download-stats.d.ts +4 -0
  39. package/dist/plugin-distribution/download-stats.js +19 -0
  40. package/dist/plugin-distribution/index.d.ts +4 -0
  41. package/dist/plugin-distribution/index.js +4 -0
  42. package/dist/plugin-distribution/installer.d.ts +3 -0
  43. package/dist/plugin-distribution/installer.js +26 -3
  44. package/dist/plugin-distribution/manager.d.ts +71 -5
  45. package/dist/plugin-distribution/manager.js +376 -37
  46. package/dist/plugin-distribution/nostr-registry-adapter.d.ts +29 -0
  47. package/dist/plugin-distribution/nostr-registry-adapter.js +152 -0
  48. package/dist/plugin-distribution/plugin-bundle-verifier.d.ts +43 -0
  49. package/dist/plugin-distribution/plugin-bundle-verifier.js +64 -1
  50. package/dist/plugin-distribution/plugin-bundle.d.ts +14 -0
  51. package/dist/plugin-distribution/plugin-bundle.js +46 -7
  52. package/dist/plugin-distribution/plugin-markdown.d.ts +6 -0
  53. package/dist/plugin-distribution/plugin-markdown.js +94 -0
  54. package/dist/plugin-distribution/registry-client.d.ts +6 -1
  55. package/dist/plugin-distribution/registry-client.js +32 -10
  56. package/dist/plugin-distribution/registry-source-state.d.ts +15 -0
  57. package/dist/plugin-distribution/registry-source-state.js +42 -0
  58. package/dist/plugin-distribution/schemas.d.ts +2511 -2824
  59. package/dist/plugin-distribution/schemas.js +111 -42
  60. package/dist/plugin-distribution/types.d.ts +116 -5
  61. package/dist/plugin-distribution-host.d.ts +4 -0
  62. package/dist/plugin-distribution-host.js +2 -0
  63. package/dist/plugin-install-progress.js +1 -1
  64. package/dist/plugin-manager.d.ts +3 -0
  65. package/dist/plugin-manager.js +21 -13
  66. package/dist/plugin-settings.d.ts +21 -0
  67. package/dist/plugin-settings.js +41 -0
  68. package/dist/plugin.d.ts +13 -0
  69. package/dist/plugin.js +56 -0
  70. package/dist/registry-schema/catalog-v1.d.ts +4143 -0
  71. package/dist/registry-schema/catalog-v1.js +172 -0
  72. package/dist/registry-schema/catalog-v2.d.ts +5275 -0
  73. package/dist/registry-schema/catalog-v2.js +137 -0
  74. package/dist/registry-schema/index.d.ts +1 -0
  75. package/dist/registry-schema/index.js +1 -0
  76. package/dist/registry-schema/release-notes.d.ts +5 -0
  77. package/dist/registry-schema/release-notes.js +21 -0
  78. package/dist/server-host.d.ts +28 -0
  79. package/dist/server-host.js +17 -0
  80. package/dist/storage/app-database.d.ts +37 -2
  81. package/dist/storage/app-database.js +41 -1
  82. package/dist/storage/browser-coordinated-app-database.d.ts +3 -1
  83. package/dist/storage/browser-coordinated-app-database.js +8 -0
  84. package/dist/storage/browser.d.ts +4 -1
  85. package/dist/storage/browser.js +84 -0
  86. package/dist/storage/desktop-native.d.ts +4 -2
  87. package/dist/storage/desktop-native.js +90 -1
  88. package/dist/storage/fs.d.ts +17 -0
  89. package/dist/storage/index.d.ts +1 -0
  90. package/dist/storage/index.js +1 -0
  91. package/dist/storage/memory-vault-adapter.d.ts +3 -1
  92. package/dist/storage/memory-vault-adapter.js +60 -0
  93. package/dist/storage/remote-app-database.d.ts +19 -0
  94. package/dist/storage/remote-app-database.js +130 -0
  95. package/dist/storage/task-projection.d.ts +2 -1
  96. package/dist/storage/task-projection.js +7 -2
  97. package/dist/storage/turso-app-database.d.ts +3 -1
  98. package/dist/storage/turso-app-database.js +45 -0
  99. package/dist/storage/vault-session.js +11 -0
  100. package/dist/storage/vault-state.d.ts +1 -1
  101. package/dist/storage/vault.svelte.d.ts +3 -1
  102. package/dist/storage/vault.svelte.js +76 -3
  103. package/dist/sync-host-security.d.ts +285 -0
  104. package/dist/sync-host-security.js +2237 -0
  105. package/dist/sync-host.d.ts +179 -0
  106. package/dist/sync-host.js +769 -0
  107. package/dist/workspace.svelte.d.ts +1 -1
  108. package/dist/workspace.svelte.js +1 -1
  109. package/package.json +48 -10
@@ -0,0 +1,137 @@
1
+ import { z } from "zod";
2
+ import { registryCatalogEntrySchema, registryCatalogDetailSchema, registryCatalogStatusSchema, registryImageReferenceSchema, registryReleaseSchema, registryReleaseChangelogSchema, } from "./catalog-v1.js";
3
+ export { registryCatalogStatusSchema, registryImageReferenceSchema, registryReleaseChangelogSchema, };
4
+ /** Authored variants remain optional; display fallbacks never rewrite metadata. */
5
+ export const registryImageSetSchema = z
6
+ .object({
7
+ preview: registryImageReferenceSchema.optional(),
8
+ full: registryImageReferenceSchema.optional(),
9
+ light: registryImageReferenceSchema.optional(),
10
+ dark: registryImageReferenceSchema.optional(),
11
+ })
12
+ .refine((images) => Object.values(images).some(Boolean), "At least one image variant is required");
13
+ export const registryMediaV2Schema = z.object({
14
+ alt: z.string().min(1),
15
+ images: registryImageSetSchema,
16
+ });
17
+ export const registryGalleryV2Schema = registryMediaV2Schema.extend({
18
+ id: z.string().min(1),
19
+ });
20
+ export const registryAppearanceV2Schema = z.object({
21
+ icon: z.string().min(1),
22
+ accent: z.string().min(1),
23
+ logo: registryMediaV2Schema.optional(),
24
+ });
25
+ export const registryReleaseV2Schema = registryReleaseSchema.omit({
26
+ releaseEventId: true,
27
+ manifestSha256: true,
28
+ releaseSequence: true,
29
+ });
30
+ export const registryCatalogV2EntrySchema = registryCatalogEntrySchema.extend({
31
+ schemaVersion: z.literal(2).optional(),
32
+ appearance: registryAppearanceV2Schema.optional(),
33
+ links: registryCatalogDetailSchema.shape.links,
34
+ });
35
+ export const registryCatalogV2IndexSchema = z.object({
36
+ schemaVersion: z.literal(2),
37
+ generatedAt: z.string().datetime({ offset: true }),
38
+ plugins: z.array(registryCatalogV2EntrySchema),
39
+ });
40
+ export const registryCatalogV2DetailSchema = registryCatalogDetailSchema.extend({
41
+ schemaVersion: z.literal(2),
42
+ appearance: registryAppearanceV2Schema.optional(),
43
+ media: z
44
+ .object({
45
+ banner: registryMediaV2Schema.optional(),
46
+ overview: registryMediaV2Schema.optional(),
47
+ })
48
+ .optional(),
49
+ gallery: z.array(registryGalleryV2Schema).optional(),
50
+ versions: z.record(z.string().min(1), registryReleaseV2Schema),
51
+ });
52
+ export function resolveRegistryMedia(media, variant = "preview") {
53
+ const images = media.images;
54
+ const reference = images[variant] ??
55
+ images.full ??
56
+ images.light ??
57
+ images.dark ??
58
+ images.preview;
59
+ if (!reference)
60
+ throw new Error("Media has no usable image variant");
61
+ return reference;
62
+ }
63
+ function record(value) {
64
+ if (!value || typeof value !== "object" || Array.isArray(value))
65
+ throw new Error("Expected registry object");
66
+ return value;
67
+ }
68
+ function appearance(value) {
69
+ if (value === undefined)
70
+ return undefined;
71
+ const result = record(value);
72
+ if (result.logo === undefined)
73
+ return result;
74
+ const logo = record(result.logo);
75
+ return {
76
+ ...result,
77
+ logo: logo.images ? logo : { alt: logo.alt, images: { full: logo } },
78
+ };
79
+ }
80
+ /** Call only after the source adapter has verified the original signed input. */
81
+ export function normalizeRegistryCatalogEntry(value) {
82
+ const entry = record(value);
83
+ const author = entry.author ?? record(entry.owner ?? {}).name;
84
+ return registryCatalogV2EntrySchema.parse({
85
+ ...entry,
86
+ schemaVersion: 2,
87
+ appearance: appearance(entry.appearance),
88
+ status: entry.status ?? "active",
89
+ badges: entry.badges ?? [],
90
+ links: {
91
+ ...(entry.links ?? {}),
92
+ ...(!entry.links
93
+ ?.documentation && typeof entry.readmeUrl === "string"
94
+ ? { documentation: entry.readmeUrl }
95
+ : {}),
96
+ },
97
+ owner: entry.owner ?? {
98
+ name: author,
99
+ verified: entry.channel === "official",
100
+ },
101
+ });
102
+ }
103
+ export function normalizeRegistryCatalogIndex(value) {
104
+ const index = record(value);
105
+ if (index.schemaVersion !== 1 && index.schemaVersion !== 2)
106
+ throw new Error("Unsupported registry schema version");
107
+ if (!Array.isArray(index.plugins))
108
+ throw new Error("Expected registry plugin list");
109
+ return registryCatalogV2IndexSchema.parse({
110
+ ...index,
111
+ schemaVersion: 2,
112
+ plugins: index.plugins.map(normalizeRegistryCatalogEntry),
113
+ });
114
+ }
115
+ export function normalizeRegistryCatalogDetail(value) {
116
+ const detail = record(value);
117
+ if (detail.schemaVersion !== 1 && detail.schemaVersion !== 2)
118
+ throw new Error("Unsupported registry schema version");
119
+ const owner = record(detail.owner ?? {});
120
+ return registryCatalogV2DetailSchema.parse({
121
+ ...detail,
122
+ schemaVersion: 2,
123
+ author: detail.author ?? owner.name,
124
+ authorUrl: detail.authorUrl ?? owner.url,
125
+ status: detail.status ?? "active",
126
+ links: {
127
+ ...(detail.links ?? {}),
128
+ ...(!detail.links
129
+ ?.documentation && typeof detail.readmeUrl === "string"
130
+ ? { documentation: detail.readmeUrl }
131
+ : {}),
132
+ },
133
+ owner: { ...owner, verified: owner.verified ?? false },
134
+ appearance: appearance(detail.appearance),
135
+ });
136
+ }
137
+ export { registryChangelogFromMarkdown } from "./release-notes.js";
@@ -0,0 +1 @@
1
+ export * from "./catalog-v2.js";
@@ -0,0 +1 @@
1
+ export * from "./catalog-v2.js";
@@ -0,0 +1,5 @@
1
+ export declare function registryChangelogFromMarkdown(name: string, version: string, markdown: string, override?: unknown): {
2
+ title: string;
3
+ description?: string | undefined;
4
+ tags?: ("security" | "fix" | "feature" | "improvement" | "breaking" | "deprecation" | "removal")[] | undefined;
5
+ } | undefined;
@@ -0,0 +1,21 @@
1
+ import { registryReleaseChangelogSchema } from "./catalog-v1.js";
2
+ export function registryChangelogFromMarkdown(name, version, markdown, override) {
3
+ if (override !== undefined)
4
+ return registryReleaseChangelogSchema.parse(override);
5
+ const lines = markdown.split(/\r?\n/u);
6
+ const start = lines.findIndex((line) => {
7
+ const heading = /^##\s+\[?v?([^\]\s]+)\]?/u.exec(line);
8
+ return heading?.[1] === version;
9
+ });
10
+ if (start < 0)
11
+ return undefined;
12
+ const end = lines.findIndex((line, index) => index > start && /^##\s/u.test(line));
13
+ const description = lines
14
+ .slice(start + 1, end < 0 ? undefined : end)
15
+ .join("\n")
16
+ .trim();
17
+ return registryReleaseChangelogSchema.parse({
18
+ title: `${name} ${version}`,
19
+ ...(description ? { description } : {}),
20
+ });
21
+ }
@@ -0,0 +1,28 @@
1
+ import type { VaultProfile } from "./storage/vault-state";
2
+ export declare const LAPIS_SERVER_PROTOCOL_MAJOR = 1;
3
+ export type ServerVaultRole = "owner" | "editor" | "viewer";
4
+ export interface ServerVaultProfile extends VaultProfile {
5
+ kind: "server-hosted";
6
+ role: ServerVaultRole;
7
+ archived: boolean;
8
+ }
9
+ export interface ServerHostCapabilities {
10
+ appDatabase: true;
11
+ fileWatch: true;
12
+ languageService: true;
13
+ pluginAssets: true;
14
+ vaultResources: true;
15
+ ai: false;
16
+ terminal: false;
17
+ collaboration: false;
18
+ multiInstance: false;
19
+ }
20
+ export type ServerHostErrorCode = "UNAUTHENTICATED" | "FORBIDDEN" | "NOT_FOUND" | "CONFLICT" | "PRECONDITION_FAILED" | "INVALID_PATH" | "PROTOCOL_MISMATCH" | "RESYNC_REQUIRED" | "RATE_LIMITED" | "INTERNAL";
21
+ export declare class ServerHostError extends Error {
22
+ readonly code: ServerHostErrorCode;
23
+ readonly requestId?: string | undefined;
24
+ constructor(code: ServerHostErrorCode, message: string, requestId?: string | undefined);
25
+ }
26
+ export declare class VaultConflictError extends ServerHostError {
27
+ constructor(message?: string);
28
+ }
@@ -0,0 +1,17 @@
1
+ export const LAPIS_SERVER_PROTOCOL_MAJOR = 1;
2
+ export class ServerHostError extends Error {
3
+ code;
4
+ requestId;
5
+ constructor(code, message, requestId) {
6
+ super(message);
7
+ this.code = code;
8
+ this.requestId = requestId;
9
+ this.name = "ServerHostError";
10
+ }
11
+ }
12
+ export class VaultConflictError extends ServerHostError {
13
+ constructor(message = "The vault file changed before it could be saved") {
14
+ super("CONFLICT", message);
15
+ this.name = "VaultConflictError";
16
+ }
17
+ }
@@ -3,7 +3,7 @@ import { type SearchQueryEvaluationOptions, type SearchQueryTextRangeOptions } f
3
3
  import { type IndexProjectionDefinitionRecord, type IndexProjectionEdgeRecord, type IndexQuery, type IndexQueryResult, type IndexRelatedQuery, type IndexProjectionRowRecord, type IndexProjectionSourceRecord, type IndexProjectionValueRecord, type MarkProjectionSourceErrorInput, type ReplaceProjectionSourceInput } from "./index-projection";
4
4
  import { type AppDatabaseLinkKind, type AppDatabaseTaskChildQuery, type AppDatabaseTaskQuery, type AppDatabaseTaskRecord } from "./task-projection";
5
5
  export type { SearchEmbeddingProviderConfig, SearchEmbeddingRuntimeStatus, } from "./search-embedding-provider";
6
- export type RuntimeTarget = "web-pwa" | "deno-desktop" | "test";
6
+ export type RuntimeTarget = "web-pwa" | "deno-desktop" | "deno-server" | "test";
7
7
  export interface LocalSearchQueryEnhancementProviderConfig {
8
8
  kind: "local-model";
9
9
  queryExpansionModelId: string;
@@ -79,7 +79,7 @@ export interface AppDatabaseSearchIndexStats {
79
79
  export declare function supportsSearchQueryEnhancementRuntime(runtimeTarget?: RuntimeTarget | null): boolean;
80
80
  export type AppDatabaseKind = "turso-wasm" | "turso-native" | "memory";
81
81
  export type AppDatabaseStorageMode = "local" | "synced" | "remote";
82
- export type AppDatabaseTransport = "native" | "wasm-worker" | "broadcast-proxy" | "memory";
82
+ export type AppDatabaseTransport = "native" | "http-rpc" | "wasm-worker" | "broadcast-proxy" | "memory";
83
83
  export type AppDatabaseRole = "direct" | "owner" | "proxy" | "blocked" | "test";
84
84
  export interface AppDatabaseCapabilities {
85
85
  nativeFullTextSearch: boolean;
@@ -264,6 +264,37 @@ export interface AppDatabaseChangeSet {
264
264
  reset?: boolean;
265
265
  committedAt: number;
266
266
  }
267
+ /** A namespaced application-metadata row returned without exposing storage. */
268
+ export interface AppDatabaseMetaEntry<T = unknown> {
269
+ key: string;
270
+ value: T;
271
+ }
272
+ export interface AppDatabaseMetaScanQuery {
273
+ prefix: string;
274
+ after?: string;
275
+ limit?: number;
276
+ }
277
+ export interface AppDatabaseMetaScanPage<T = unknown> {
278
+ entries: AppDatabaseMetaEntry<T>[];
279
+ nextCursor?: string;
280
+ }
281
+ export interface AppDatabaseMetaCheck {
282
+ key: string;
283
+ exists?: boolean;
284
+ value?: unknown;
285
+ }
286
+ export interface AppDatabaseMetaMutation {
287
+ key: string;
288
+ value?: unknown;
289
+ delete?: boolean;
290
+ }
291
+ export interface AppDatabaseMetaTransaction {
292
+ checks?: AppDatabaseMetaCheck[];
293
+ mutations: AppDatabaseMetaMutation[];
294
+ }
295
+ export interface AppDatabaseMetaTransactionResult {
296
+ applied: boolean;
297
+ }
267
298
  export type AppDatabaseChangeListener = (change: AppDatabaseChangeSet) => void;
268
299
  export interface SearchDocumentRecord {
269
300
  path: string;
@@ -591,6 +622,8 @@ export interface AppDatabase {
591
622
  getSearchIndexStats(): Promise<AppDatabaseSearchIndexStats>;
592
623
  getMeta<T = unknown>(key: string): Promise<T | undefined>;
593
624
  setMeta(key: string, value: unknown): Promise<void>;
625
+ scanMeta<T = unknown>(query: AppDatabaseMetaScanQuery): Promise<AppDatabaseMetaScanPage<T>>;
626
+ transactMeta(transaction: AppDatabaseMetaTransaction): Promise<AppDatabaseMetaTransactionResult>;
594
627
  getNotebookState(sourcePath: string): Promise<AppDatabaseNotebookState | undefined>;
595
628
  setNotebookState(sourcePath: string, state: AppDatabaseNotebookState): Promise<void>;
596
629
  deleteNotebookState(sourcePath: string): Promise<void>;
@@ -770,6 +803,8 @@ export declare class MemoryAppDatabase implements AppDatabase {
770
803
  getSearchIndexStats(): Promise<AppDatabaseSearchIndexStats>;
771
804
  getMeta<T = unknown>(key: string): Promise<T | undefined>;
772
805
  setMeta(key: string, value: unknown): Promise<void>;
806
+ scanMeta<T = unknown>({ prefix, after, limit, }: AppDatabaseMetaScanQuery): Promise<AppDatabaseMetaScanPage<T>>;
807
+ transactMeta({ checks, mutations, }: AppDatabaseMetaTransaction): Promise<AppDatabaseMetaTransactionResult>;
773
808
  getNotebookState(sourcePath: string): Promise<AppDatabaseNotebookState | undefined>;
774
809
  setNotebookState(sourcePath: string, state: AppDatabaseNotebookState): Promise<void>;
775
810
  deleteNotebookState(sourcePath: string): Promise<void>;
@@ -1047,9 +1047,49 @@ export class MemoryAppDatabase {
1047
1047
  return clone(this.meta[key]);
1048
1048
  }
1049
1049
  async setMeta(key, value) {
1050
- this.meta[key] = clone(value);
1050
+ if (value === undefined)
1051
+ delete this.meta[key];
1052
+ else
1053
+ this.meta[key] = clone(value);
1051
1054
  this.emitChange(["meta"]);
1052
1055
  }
1056
+ async scanMeta({ prefix, after, limit = 100, }) {
1057
+ const boundedLimit = Math.max(1, Math.min(500, Math.floor(limit)));
1058
+ const keys = Object.keys(this.meta)
1059
+ .filter((key) => key.startsWith(prefix) && (!after || key > after))
1060
+ .sort();
1061
+ const pageKeys = keys.slice(0, boundedLimit);
1062
+ return {
1063
+ entries: pageKeys.map((key) => ({
1064
+ key,
1065
+ value: clone(this.meta[key]),
1066
+ })),
1067
+ nextCursor: keys.length > pageKeys.length ? pageKeys.at(-1) : undefined,
1068
+ };
1069
+ }
1070
+ async transactMeta({ checks = [], mutations, }) {
1071
+ for (const check of checks) {
1072
+ const exists = Object.prototype.hasOwnProperty.call(this.meta, check.key);
1073
+ if (check.exists !== undefined && exists !== check.exists) {
1074
+ return { applied: false };
1075
+ }
1076
+ if ("value" in check &&
1077
+ JSON.stringify(this.meta[check.key]) !== JSON.stringify(check.value)) {
1078
+ return { applied: false };
1079
+ }
1080
+ }
1081
+ for (const mutation of mutations) {
1082
+ if (mutation.delete || mutation.value === undefined) {
1083
+ delete this.meta[mutation.key];
1084
+ }
1085
+ else {
1086
+ this.meta[mutation.key] = clone(mutation.value);
1087
+ }
1088
+ }
1089
+ if (mutations.length > 0)
1090
+ this.emitChange(["meta"]);
1091
+ return { applied: true };
1092
+ }
1053
1093
  async getNotebookState(sourcePath) {
1054
1094
  return this.getMeta(notebookStateMetaKey(sourcePath));
1055
1095
  }
@@ -1,4 +1,4 @@
1
- import type { AppDatabase, AppDatabaseChangeListener, AppDatabaseDescriptor, AppDatabaseFileHistory, AppDatabaseIndexedFile, AppDatabaseIndexedFileManifestPage, AppDatabaseIndexedFileManifestQuery, AppDatabaseIndexedMetadataPage, AppDatabaseIndexedMetadataPageQuery, AppDatabaseIndexedMetadataQuery, AppDatabaseIndexedMetadataRow, AppDatabaseKind, AppDatabaseMetadataFacetQuery, AppDatabaseMetadataFacetRow, AppDatabaseMetadataLinkQuery, AppDatabaseMemoryCandidateInput, AppDatabaseMemoryCandidateQuery, AppDatabaseMemoryCandidateResult, AppDatabaseMemoryJobClaimInput, AppDatabaseMemoryJobRecord, AppDatabaseMemoryJobUpdateInput, AppDatabaseMemoryRecallSignalRecord, AppDatabaseMemorySourceRecord, AppDatabaseProvider, AppDatabaseNotificationRecord, AppDatabaseSearchIndexStats, AppDatabaseNotebookState, AppDatabaseSearchOptions, AppDatabaseSearchResult, AppDatabaseStoreFileHistoryRevisionInput, AppDatabaseStoreFileHistoryRevisionResult, MetadataCacheSnapshot, SearchDocumentRecord, SearchDocumentManifestPage, SearchDocumentManifestQuery, SearchEmbeddingProviderConfig, SearchEmbeddingRuntimeStatus, AppDatabaseLinkRecord } from "./app-database";
1
+ import type { AppDatabase, AppDatabaseChangeListener, AppDatabaseDescriptor, AppDatabaseFileHistory, AppDatabaseIndexedFile, AppDatabaseIndexedFileManifestPage, AppDatabaseIndexedFileManifestQuery, AppDatabaseIndexedMetadataPage, AppDatabaseIndexedMetadataPageQuery, AppDatabaseIndexedMetadataQuery, AppDatabaseIndexedMetadataRow, AppDatabaseKind, AppDatabaseMetadataFacetQuery, AppDatabaseMetadataFacetRow, AppDatabaseMetadataLinkQuery, AppDatabaseMemoryCandidateInput, AppDatabaseMemoryCandidateQuery, AppDatabaseMemoryCandidateResult, AppDatabaseMemoryJobClaimInput, AppDatabaseMemoryJobRecord, AppDatabaseMemoryJobUpdateInput, AppDatabaseMemoryRecallSignalRecord, AppDatabaseMemorySourceRecord, AppDatabaseMetaScanPage, AppDatabaseMetaScanQuery, AppDatabaseMetaTransaction, AppDatabaseMetaTransactionResult, AppDatabaseProvider, AppDatabaseNotificationRecord, AppDatabaseSearchIndexStats, AppDatabaseNotebookState, AppDatabaseSearchOptions, AppDatabaseSearchResult, AppDatabaseStoreFileHistoryRevisionInput, AppDatabaseStoreFileHistoryRevisionResult, MetadataCacheSnapshot, SearchDocumentRecord, SearchDocumentManifestPage, SearchDocumentManifestQuery, SearchEmbeddingProviderConfig, SearchEmbeddingRuntimeStatus, AppDatabaseLinkRecord } from "./app-database";
2
2
  import type { AppDatabaseTaskChildQuery, AppDatabaseTaskQuery, AppDatabaseTaskRecord } from "./task-projection";
3
3
  import { BrowserAppDatabaseCoordinator } from "./browser-app-database-coordination";
4
4
  export type BrowserCoordinatedAppDatabaseMode = "turso-owner" | "turso-proxy";
@@ -36,6 +36,8 @@ export declare class BrowserCoordinatedAppDatabase implements AppDatabase {
36
36
  getSearchIndexStats(): Promise<AppDatabaseSearchIndexStats>;
37
37
  getMeta<T = unknown>(key: string): Promise<T | undefined>;
38
38
  setMeta(key: string, value: unknown): Promise<void>;
39
+ scanMeta<T = unknown>(query: AppDatabaseMetaScanQuery): Promise<AppDatabaseMetaScanPage<T>>;
40
+ transactMeta(transaction: AppDatabaseMetaTransaction): Promise<AppDatabaseMetaTransactionResult>;
39
41
  getNotebookState(sourcePath: string): Promise<AppDatabaseNotebookState | undefined>;
40
42
  setNotebookState(sourcePath: string, state: AppDatabaseNotebookState): Promise<void>;
41
43
  deleteNotebookState(sourcePath: string): Promise<void>;
@@ -16,6 +16,8 @@ const APP_DATABASE_RPC_METHODS = new Set([
16
16
  "getSearchIndexStats",
17
17
  "getMeta",
18
18
  "setMeta",
19
+ "scanMeta",
20
+ "transactMeta",
19
21
  "getNotebookState",
20
22
  "setNotebookState",
21
23
  "deleteNotebookState",
@@ -269,6 +271,12 @@ export class BrowserCoordinatedAppDatabase {
269
271
  async setMeta(key, value) {
270
272
  await this.invoke("setMeta", key, value);
271
273
  }
274
+ async scanMeta(query) {
275
+ return this.invoke("scanMeta", query);
276
+ }
277
+ async transactMeta(transaction) {
278
+ return this.invoke("transactMeta", transaction);
279
+ }
272
280
  async getNotebookState(sourcePath) {
273
281
  return this.invoke("getNotebookState", sourcePath);
274
282
  }
@@ -1,4 +1,4 @@
1
- import type { DataAdapter, DataWriteOptions, ListedFiles, Stat, AsyncResourceAdapter, VaultIdentityAdapter } from "./fs";
1
+ import type { DataAdapter, DataWriteOptions, AtomicDataWriteOptions, ListedFiles, Stat, AsyncResourceAdapter, VaultIdentityAdapter, VaultReadHandle } from "./fs";
2
2
  import { ScopedVaultStore, type KeyValueStore, type VaultProfile } from "./vault-state";
3
3
  export type BrowserFileSystemPermissionMode = "read" | "readwrite";
4
4
  export type BrowserFileSystemPermissionState = "granted" | "denied" | "prompt";
@@ -15,6 +15,7 @@ export interface BrowserFileSystemWritableFileStream {
15
15
  write(data: ArrayBuffer | Blob | string | Uint8Array): Promise<void>;
16
16
  seek(position: number): Promise<void>;
17
17
  close(): Promise<void>;
18
+ abort?(reason?: unknown): Promise<void>;
18
19
  }
19
20
  export interface BrowserFileSystemCreateWritableOptions {
20
21
  keepExistingData?: boolean;
@@ -102,6 +103,8 @@ export declare class BrowserHandleVaultAdapter implements DataAdapter, VaultIden
102
103
  stat(normalizedPath: string): Promise<Stat | null>;
103
104
  read(normalizedPath: string): Promise<string>;
104
105
  readBinary(normalizedPath: string): Promise<ArrayBuffer>;
106
+ openRead(normalizedPath: string): Promise<VaultReadHandle>;
107
+ atomicWrite(normalizedPath: string, source: ReadableStream<Uint8Array>, options?: AtomicDataWriteOptions): Promise<void>;
105
108
  write(normalizedPath: string, data: string, options?: DataWriteOptions): Promise<void>;
106
109
  writeBinary(normalizedPath: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
107
110
  private writeFile;
@@ -405,6 +405,90 @@ export class BrowserHandleVaultAdapter {
405
405
  return toArrayBuffer(await (await handle.getFile()).arrayBuffer());
406
406
  });
407
407
  }
408
+ async openRead(normalizedPath) {
409
+ const path = normalizeVaultPath(normalizedPath);
410
+ const file = await this.withHandleRetry(async () => {
411
+ const handle = await this.getHandle(path);
412
+ if (!handle || handle.kind !== "file")
413
+ throw createFsError("ENOENT", path);
414
+ return handle.getFile();
415
+ });
416
+ return {
417
+ size: file.size,
418
+ mtime: file.lastModified ?? Date.now(),
419
+ stream: ({ offset = 0, length, signal } = {}) => {
420
+ const start = Math.max(0, Math.min(file.size, Math.floor(offset)));
421
+ const end = Math.min(file.size, start + Math.max(0, Math.floor(length ?? file.size - start)));
422
+ const slice = file.slice(start, end);
423
+ const stream = typeof slice.stream === "function"
424
+ ? slice.stream()
425
+ : new ReadableStream({
426
+ async start(controller) {
427
+ controller.enqueue(new Uint8Array(await slice.arrayBuffer()));
428
+ controller.close();
429
+ },
430
+ });
431
+ if (!signal)
432
+ return stream;
433
+ const reader = stream.getReader();
434
+ return new ReadableStream({
435
+ async pull(controller) {
436
+ if (signal.aborted) {
437
+ await reader.cancel(signal.reason);
438
+ controller.error(signal.reason ?? new DOMException("Aborted", "AbortError"));
439
+ return;
440
+ }
441
+ const next = await reader.read();
442
+ if (next.done)
443
+ controller.close();
444
+ else
445
+ controller.enqueue(next.value);
446
+ },
447
+ cancel(reason) {
448
+ return reader.cancel(reason);
449
+ },
450
+ });
451
+ },
452
+ };
453
+ }
454
+ async atomicWrite(normalizedPath, source, options = {}) {
455
+ const path = normalizeVaultPath(normalizedPath);
456
+ await this.withPathLock(path, () => this.withHandleRetry(async () => {
457
+ const [parent, name] = await this.getParentDirectory(path, { create: true });
458
+ const existing = await this.readStoredStat(path);
459
+ const handle = await parent.getFileHandle(name, { create: true });
460
+ const writable = await handle.createWritable({ keepExistingData: false });
461
+ const reader = source.getReader();
462
+ let size = 0;
463
+ try {
464
+ while (true) {
465
+ if (options.signal?.aborted) {
466
+ throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
467
+ }
468
+ const next = await reader.read();
469
+ if (next.done)
470
+ break;
471
+ await writable.write(next.value);
472
+ size += next.value.byteLength;
473
+ }
474
+ await writable.close();
475
+ }
476
+ catch (error) {
477
+ await writable.abort?.(error).catch(() => undefined);
478
+ throw error;
479
+ }
480
+ finally {
481
+ reader.releaseLock();
482
+ }
483
+ const now = Date.now();
484
+ await this.writeStoredStat(path, {
485
+ type: "file",
486
+ ctime: options.ctime ?? existing?.ctime ?? now,
487
+ mtime: options.mtime ?? now,
488
+ size,
489
+ });
490
+ }));
491
+ }
408
492
  async write(normalizedPath, data, options) {
409
493
  await this.writeFile(normalizedPath, data, options);
410
494
  }
@@ -6,7 +6,7 @@
6
6
  * `getNativeDesktopBridge()` / `hasNativeDesktopBridge()`.
7
7
  */
8
8
  import type { AppDatabaseChangeSet, AppDatabaseNotificationSeverity } from "./app-database";
9
- import type { ChangeEvent, AsyncResourceAdapter, DataWriteOptions, ListedFiles, NativeWatchAdapter, NativeWatchSubscription, Stat, VaultAdapter, VaultAdapterCapabilities, VaultIdentityAdapter, WatchErrorEvent, WatchOptions } from "./fs";
9
+ import type { ChangeEvent, AsyncResourceAdapter, DataWriteOptions, ListedFiles, NativeWatchAdapter, NativeWatchSubscription, Stat, VaultAdapter, VaultAdapterCapabilities, VaultReadHandle, AtomicDataWriteOptions, VaultIdentityAdapter, WatchErrorEvent, WatchOptions } from "./fs";
10
10
  import { type KeyValueStore, type VaultProfile } from "./vault-state";
11
11
  export type NativeDesktopRuntime = "deno-desktop";
12
12
  export type NativeDesktopPlatformOs = "macos" | "windows" | "linux" | "unknown";
@@ -18,7 +18,7 @@ export interface NativeDesktopPlatformInfo {
18
18
  readonly appVersion?: string;
19
19
  readonly packaged?: boolean;
20
20
  }
21
- export type NativeDesktopCapabilityId = "resource" | "database" | "search" | "notebook" | "language-service" | "model" | "plugin-sidecar" | "plugin-assets" | "file-watch" | "notifications" | "file-system-actions" | "agent-runtime" | "terminal-runtime";
21
+ export type NativeDesktopCapabilityId = "resource" | "database" | "search" | "notebook" | "language-service" | "model" | "plugin-sidecar" | "plugin-assets" | "file-watch" | "notifications" | "file-system-actions" | "nostr-signer" | "agent-runtime" | "terminal-runtime" | "secure-storage";
22
22
  export type NativeAgentProcessMessage = {
23
23
  processId: string;
24
24
  type: "stdout" | "stderr" | "exit";
@@ -151,6 +151,8 @@ export declare class NativeDesktopVaultAdapter implements VaultAdapter, VaultIde
151
151
  stat(normalizedPath: string): Promise<Stat | null>;
152
152
  read(normalizedPath: string): Promise<string>;
153
153
  readBinary(normalizedPath: string): Promise<ArrayBuffer>;
154
+ openRead(normalizedPath: string): Promise<VaultReadHandle>;
155
+ atomicWrite(normalizedPath: string, source: ReadableStream<Uint8Array>, options?: AtomicDataWriteOptions): Promise<void>;
154
156
  write(normalizedPath: string, data: string, options?: DataWriteOptions): Promise<void>;
155
157
  writeBinary(normalizedPath: string, data: ArrayBuffer, options?: DataWriteOptions): Promise<void>;
156
158
  append(normalizedPath: string, data: string, options?: DataWriteOptions): Promise<void>;
@@ -133,7 +133,15 @@ function createFsError(code, path) {
133
133
  }
134
134
  function toNativeError(error) {
135
135
  if (error instanceof Error) {
136
- return error;
136
+ const nativeError = error;
137
+ if (!nativeError.code) {
138
+ // Deno's binding serializes a native Error to text, then rejects with a
139
+ // fresh Error. Restore the same portable code handled for string errors.
140
+ const match = /^(?:Error:\s*)?([A-Z0-9_]+):\s*(.*)$/u.exec(nativeError.message);
141
+ if (match)
142
+ nativeError.code = match[1];
143
+ }
144
+ return nativeError;
137
145
  }
138
146
  const message = typeof error === "string" ? error : String(error);
139
147
  const match = /^([A-Z0-9_]+):\s*(.*)$/u.exec(message);
@@ -276,6 +284,87 @@ export class NativeDesktopVaultAdapter {
276
284
  });
277
285
  return new Uint8Array(data).buffer;
278
286
  }
287
+ async openRead(normalizedPath) {
288
+ const path = normalizeVaultPath(normalizedPath);
289
+ const rootPath = this.rootPath;
290
+ const stat = await this.stat(path);
291
+ if (!stat || stat.type !== "file")
292
+ throw createFsError("ENOENT", path);
293
+ return {
294
+ size: stat.size,
295
+ mtime: stat.mtime,
296
+ stream: ({ offset = 0, length, signal } = {}) => {
297
+ let cursor = Math.max(0, Math.min(stat.size, Math.floor(offset)));
298
+ const end = Math.min(stat.size, cursor + Math.max(0, Math.floor(length ?? stat.size - cursor)));
299
+ return new ReadableStream({
300
+ async pull(controller) {
301
+ if (signal?.aborted) {
302
+ controller.error(signal.reason ?? new DOMException("Aborted", "AbortError"));
303
+ return;
304
+ }
305
+ if (cursor >= end) {
306
+ controller.close();
307
+ return;
308
+ }
309
+ const data = await invokeNative("desktop_fs_read_range", {
310
+ rootPath,
311
+ normalizedPath: path,
312
+ offset: cursor,
313
+ length: Math.min(256 * 1024, end - cursor),
314
+ });
315
+ const bytes = Uint8Array.from(data);
316
+ if (bytes.byteLength === 0) {
317
+ controller.close();
318
+ return;
319
+ }
320
+ cursor += bytes.byteLength;
321
+ controller.enqueue(bytes);
322
+ },
323
+ });
324
+ },
325
+ };
326
+ }
327
+ async atomicWrite(normalizedPath, source, options = {}) {
328
+ const path = normalizeVaultPath(normalizedPath);
329
+ const operationId = await invokeNative("desktop_fs_atomic_write_begin", {
330
+ rootPath: this.rootPath,
331
+ normalizedPath: path,
332
+ options: { ctime: options.ctime, mtime: options.mtime },
333
+ });
334
+ const reader = source.getReader();
335
+ try {
336
+ while (true) {
337
+ if (options.signal?.aborted) {
338
+ throw options.signal.reason ?? new DOMException("Aborted", "AbortError");
339
+ }
340
+ const next = await reader.read();
341
+ if (next.done)
342
+ break;
343
+ await invokeNative("desktop_fs_atomic_write_chunk", {
344
+ rootPath: this.rootPath,
345
+ normalizedPath: path,
346
+ operationId,
347
+ data: [...next.value],
348
+ });
349
+ }
350
+ await invokeNative("desktop_fs_atomic_write_commit", {
351
+ rootPath: this.rootPath,
352
+ normalizedPath: path,
353
+ operationId,
354
+ });
355
+ }
356
+ catch (error) {
357
+ await invokeNative("desktop_fs_atomic_write_abort", {
358
+ rootPath: this.rootPath,
359
+ normalizedPath: path,
360
+ operationId,
361
+ }).catch(() => undefined);
362
+ throw error;
363
+ }
364
+ finally {
365
+ reader.releaseLock();
366
+ }
367
+ }
279
368
  async write(normalizedPath, data, options) {
280
369
  await invokeNative("desktop_fs_write_text", {
281
370
  rootPath: this.rootPath,
@@ -24,6 +24,21 @@ export interface FileStats extends FileProperties {
24
24
  size: number;
25
25
  }
26
26
  export type DataWriteOptions = Partial<FileProperties>;
27
+ export interface VaultReadStreamOptions {
28
+ offset?: number;
29
+ length?: number;
30
+ signal?: AbortSignal;
31
+ }
32
+ export interface VaultReadHandle {
33
+ size: number;
34
+ mtime: number;
35
+ stream(options?: VaultReadStreamOptions): ReadableStream<Uint8Array>;
36
+ }
37
+ export interface AtomicDataWriteOptions extends DataWriteOptions {
38
+ signal?: AbortSignal;
39
+ /** Opaque host-issued correlation used by the App's mutation broker. */
40
+ operationOrigin?: string;
41
+ }
27
42
  export declare abstract class TAbstractFile {
28
43
  parent: TFolder | null;
29
44
  path: string;
@@ -140,6 +155,8 @@ export interface VaultAdapterCapabilities {
140
155
  */
141
156
  export interface VaultAdapter extends DataAdapter {
142
157
  getCapabilities?(): Partial<VaultAdapterCapabilities>;
158
+ openRead?(normalizedPath: string): Promise<VaultReadHandle>;
159
+ atomicWrite?(normalizedPath: string, source: ReadableStream<Uint8Array>, options?: AtomicDataWriteOptions): Promise<void>;
143
160
  }
144
161
  export interface VaultIdentityAdapter {
145
162
  getVaultId(): string;