alinea 2.0.0-preview.8 → 2.0.0-preview.9

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 (165) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/LICENSES.md +136 -136
  3. package/dist/adapter/next/cms.js +24 -8
  4. package/dist/adapter/next/handler.js +63 -10
  5. package/dist/adapter/next/renderStats.d.ts +25 -4
  6. package/dist/adapter/next/renderStats.js +71 -3
  7. package/dist/adapter/next/syncCheck.js +2 -8
  8. package/dist/adapter/next/with-alinea.js +6 -2
  9. package/dist/backend/HandleAction.d.ts +1 -1
  10. package/dist/backend/HandleAction.js +1 -1
  11. package/dist/backend/Handler.js +12 -35
  12. package/dist/backend/api/CreateBackend.js +6 -6
  13. package/dist/backend/api/DatabaseApi.js +2 -2
  14. package/dist/backend/api/ProxyMedia.d.ts +2 -3
  15. package/dist/backend/api/ProxyMedia.js +7 -22
  16. package/dist/backend/api/database.js +6 -6
  17. package/dist/backend/store/GeneratedDatabase.d.ts +6 -3
  18. package/dist/backend/store/GeneratedDatabase.js +13 -11
  19. package/dist/backend/store/GeneratedDatabaseNode.d.ts +1 -1
  20. package/dist/backend/store/GeneratedDatabaseNode.js +1 -1
  21. package/dist/bundled.d.ts +280 -211
  22. package/dist/chunks/{node-sqlite-7CSGL27V.js → bun-sqlite-5SHXN3AT.js} +3 -3
  23. package/dist/chunks/{chunk-IL7FJQAY.js → chunk-66YZ47TY.js} +1 -1
  24. package/dist/chunks/{chunk-M7KDJEGH.js → chunk-AIU6CMVT.js} +1 -1
  25. package/dist/chunks/{chunk-K7LHQXI4.js → chunk-ASYKC6IE.js} +1 -1
  26. package/dist/chunks/{chunk-RWWYUNVX.js → chunk-DWR5A5KL.js} +6 -2
  27. package/dist/chunks/{chunk-NLZLRQ5U.js → chunk-ETWOY4AL.js} +1 -1
  28. package/dist/chunks/{chunk-TFMUSWGG.js → chunk-MOWR65LM.js} +72 -67
  29. package/dist/chunks/{chunk-2U43BB6P.js → chunk-QJGGYF6E.js} +5 -5
  30. package/dist/chunks/{chunk-A42DP5T7.js → chunk-QSN6RH6X.js} +1 -1
  31. package/dist/chunks/{bun-sqlite-ZZDB7YQQ.js → node-sqlite-XVNTNMCY.js} +3 -3
  32. package/dist/cli/Serve.js +1 -1
  33. package/dist/cli/bin.js +6 -2
  34. package/dist/cli/generate/DevDB.js +4 -2
  35. package/dist/cli/serve/DevServer.d.ts +2 -0
  36. package/dist/cli/serve/DevServer.js +7 -2
  37. package/dist/cloud/CloudRemote.js +1 -1
  38. package/dist/components/Breadcrumbs.js +2 -2
  39. package/dist/components/Calendar.js +7 -7
  40. package/dist/components/Checkbox.js +3 -3
  41. package/dist/components/ComboBox.js +12 -12
  42. package/dist/components/DatePicker.js +3 -3
  43. package/dist/components/DateRangePicker.js +5 -5
  44. package/dist/components/Disclosure.js +7 -7
  45. package/dist/components/Label.js +5 -5
  46. package/dist/components/List.js +37 -37
  47. package/dist/components/Menu.js +2 -2
  48. package/dist/components/Modal.js +3 -3
  49. package/dist/components/MultipleSelect.js +26 -26
  50. package/dist/components/NumberField.js +6 -6
  51. package/dist/components/ProgressCircle.js +3 -3
  52. package/dist/components/RadioGroup.js +2 -2
  53. package/dist/components/SearchField.js +7 -7
  54. package/dist/components/Select.js +7 -7
  55. package/dist/components/Surface.js +2 -2
  56. package/dist/components/Switch.js +2 -2
  57. package/dist/components/Table.js +7 -7
  58. package/dist/components/Tabs.js +3 -3
  59. package/dist/components/TagGroup.js +2 -2
  60. package/dist/components/TextField.js +4 -4
  61. package/dist/components/TimeField.js +3 -3
  62. package/dist/components/Toggle.js +2 -2
  63. package/dist/components/Toolbar.js +3 -3
  64. package/dist/components/Tree.js +7 -7
  65. package/dist/core/Client.d.ts +1 -4
  66. package/dist/core/Client.js +5 -7
  67. package/dist/core/Connection.d.ts +4 -1
  68. package/dist/core/Connection.js +3 -1
  69. package/dist/core/db/LocalStore.d.ts +2 -0
  70. package/dist/core/source/GithubSource.js +39 -11
  71. package/dist/dashboard/App.js +2 -2
  72. package/dist/dashboard/app/AccessDenied.js +4 -4
  73. package/dist/dashboard/app/ActivityStatus.js +15 -15
  74. package/dist/dashboard/app/AuthView.js +13 -13
  75. package/dist/dashboard/app/Badge.js +2 -2
  76. package/dist/dashboard/app/CompactField.js +9 -9
  77. package/dist/dashboard/app/DashboardErrorBoundary.js +7 -7
  78. package/dist/dashboard/app/DashboardLayout.js +3 -3
  79. package/dist/dashboard/app/EntryFields.js +2 -2
  80. package/dist/dashboard/app/EntryHeader.js +10 -10
  81. package/dist/dashboard/app/EntryReferences.js +7 -7
  82. package/dist/dashboard/app/EntrySidebar.js +16 -16
  83. package/dist/dashboard/app/EntrySidebarPreview.js +10 -10
  84. package/dist/dashboard/app/EntryTranslationBanner.js +3 -3
  85. package/dist/dashboard/app/Explorer.js +44 -44
  86. package/dist/dashboard/app/ExplorerCards.js +34 -34
  87. package/dist/dashboard/app/ExplorerFileCard.js +21 -21
  88. package/dist/dashboard/app/ExplorerList.js +10 -10
  89. package/dist/dashboard/app/ExplorerModal.js +4 -4
  90. package/dist/dashboard/app/ExplorerTable.js +49 -49
  91. package/dist/dashboard/app/LinkPicker.js +4 -4
  92. package/dist/dashboard/app/LocaleMenu.js +3 -3
  93. package/dist/dashboard/app/SidebarTree.js +42 -42
  94. package/dist/dashboard/app/WorkspaceMenu.js +12 -12
  95. package/dist/dashboard/app/WorkspaceRoots.js +28 -28
  96. package/dist/dashboard/app/editor/FileEditor.js +27 -27
  97. package/dist/dashboard/app/editor/FilePreview.js +7 -7
  98. package/dist/dashboard/app/modals/CreateEntry.js +3 -3
  99. package/dist/dashboard/app/pages/EntryPage.js +19 -19
  100. package/dist/dashboard/app/pages/SplashPage.js +46 -46
  101. package/dist/dashboard/app/pages/UsersPage.js +36 -36
  102. package/dist/dashboard/app/ui/DashboardModal.js +19 -19
  103. package/dist/dashboard/app/ui/Rail.js +3 -3
  104. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  105. package/dist/dashboard/boot/DashboardWorker.js +6 -2
  106. package/dist/dashboard/boot/WorkerDB.d.ts +2 -2
  107. package/dist/database/BrowserEntryStore.d.ts +18 -6
  108. package/dist/database/BrowserEntryStore.js +95 -81
  109. package/dist/database/DatabaseSource.d.ts +2 -2
  110. package/dist/database/DatabaseTables.js +2 -2
  111. package/dist/database/EntryDatabase.d.ts +83 -7
  112. package/dist/database/EntryDatabase.js +420 -30
  113. package/dist/database/EntryStore.d.ts +10 -12
  114. package/dist/database/EntryStore.js +27 -66
  115. package/dist/database/EntryTransaction.d.ts +3 -3
  116. package/dist/database/EntryTransaction.js +39 -21
  117. package/dist/database/Version.d.ts +2 -2
  118. package/dist/database/Version.js +1 -1
  119. package/dist/database/driver/DatabaseHandle.d.ts +17 -0
  120. package/dist/database/driver/DatabaseHandle.js +0 -0
  121. package/dist/database/driver/OverlayExtension.browser.d.ts +2 -0
  122. package/dist/database/driver/OverlayExtension.browser.js +9 -0
  123. package/dist/database/driver/OverlayExtension.d.ts +1 -0
  124. package/dist/database/driver/OverlayExtension.js +7 -0
  125. package/dist/database/driver/RuntimeDatabase.d.ts +11 -6
  126. package/dist/database/driver/RuntimeDatabase.js +66 -22
  127. package/dist/database/driver/WasmDatabase.d.ts +23 -2
  128. package/dist/database/driver/WasmDatabase.js +22 -9
  129. package/dist/database/entry/EntryTable.d.ts +21 -5
  130. package/dist/database/entry/EntryTable.js +59 -10
  131. package/dist/database/query/EntryQuery.d.ts +3 -2
  132. package/dist/database/query/EntryQuery.js +23 -25
  133. package/dist/database/query/EntryReferences.d.ts +2 -3
  134. package/dist/database/query/EntryReferences.js +43 -41
  135. package/dist/database/query/Relation.js +1 -1
  136. package/dist/database/query/ResolveQuery.d.ts +1 -4
  137. package/dist/database/query/ResolveQuery.js +54 -19
  138. package/dist/database/query/Search.d.ts +8 -17
  139. package/dist/database/query/Search.js +17 -37
  140. package/dist/database/sync/EntryParser.d.ts +5 -1
  141. package/dist/database/sync/EntryParser.js +4 -1
  142. package/dist/database/sync/EntrySyncer.d.ts +7 -19
  143. package/dist/database/sync/EntrySyncer.js +17 -45
  144. package/dist/database/sync/Ingest.js +40 -31
  145. package/dist/database/sync/SyncQueries.d.ts +5 -2
  146. package/dist/database/sync/SyncQueries.js +27 -3
  147. package/dist/field/check/CheckField.view.js +2 -2
  148. package/dist/field/code/CodeField.view.js +11 -11
  149. package/dist/field/link/LinkField.view.js +34 -34
  150. package/dist/field/list/ListField.view.js +17 -17
  151. package/dist/field/metadata/MetadataField.view.js +15 -15
  152. package/dist/field/richtext/RichTextBlock.js +2 -2
  153. package/dist/field/richtext/RichTextExtensions.js +23 -23
  154. package/dist/field/richtext/RichTextField.view.js +2 -2
  155. package/dist/field/richtext/RichTextInsertMenu.js +2 -2
  156. package/dist/next.cjs +6 -2
  157. package/dist/preview/widget.d.ts +29 -0
  158. package/dist/preview/widget.js +31 -5
  159. package/package.json +6 -2
  160. package/dist/adapter/next/DevRemote.d.ts +0 -3
  161. package/dist/adapter/next/DevRemote.js +0 -45
  162. package/dist/database/EntryLayer.d.ts +0 -93
  163. package/dist/database/EntryLayer.js +0 -459
  164. package/dist/database/entry/EntryView.d.ts +0 -22
  165. package/dist/database/entry/EntryView.js +0 -106
package/dist/bundled.d.ts CHANGED
@@ -93,14 +93,6 @@ declare module 'alinea/adapter/next/context' {
93
93
 
94
94
  }
95
95
 
96
- declare module 'alinea/adapter/next/DevRemote' {
97
-
98
- import type { RemoteConnection, RequestContext } from 'alinea/core/Connection';
99
- import type { Config } from 'alinea/core/Config';
100
- export function createDevRemote(context: RequestContext, config: Config): RemoteConnection;
101
-
102
- }
103
-
104
96
  declare module 'alinea/adapter/next/ForwardCredentials' {
105
97
 
106
98
  export function forwardDevelopmentCredentials(request: Request | undefined, apiKey: string, init?: RequestInit): RequestInit;
@@ -167,16 +159,32 @@ declare module 'alinea/adapter/next/previews.spec' {
167
159
  declare module 'alinea/adapter/next/renderStats' {
168
160
 
169
161
  import type { GraphQuery } from 'alinea/core/Graph';
170
- import type { PreviewStat, PreviewStats } from 'alinea/preview/widget';
162
+ import type { PreviewDatabase, PreviewStat, PreviewStatement, PreviewStats } from 'alinea/preview/widget';
171
163
  /** Collects the CMS queries and syncs of one draft render. */
172
164
  export class RenderStats {
173
165
  #private;
174
166
  rows: Array<PreviewStat>;
175
167
  statements: number;
176
168
  sqlMs: number;
169
+ slowest: Array<PreviewStatement>;
170
+ /** The bundled database that answered, once a query reached it. */
171
+ database?: PreviewDatabase;
172
+ readonly startedAt: number;
177
173
  /** Add a row and count it as pending until `run` settles; see `timed`. */
178
174
  track<T>(stat: Omit<PreviewStat, 'durationMs'>, run: (row: PreviewStat) => Promise<T>): Promise<T>;
179
- statement(durationMs: number): void;
175
+ /**
176
+ * Time a database query without the time it waited behind the others:
177
+ * queries of a render run one at a time in the order they were called, so
178
+ * one starts executing once the previous one finished, or when it was
179
+ * called if that is later.
180
+ */
181
+ executed<T>(row: PreviewStat, run: () => Promise<T>): Promise<T>;
182
+ /**
183
+ * Count a statement towards the render, and towards the row that ran it:
184
+ * a sync in progress, or else the query executing, which is the earliest
185
+ * one called that has not finished (see `executed`).
186
+ */
187
+ statement(durationMs: number, sql?: string): void;
180
188
  /**
181
189
  * Resolves once nothing ran for `quietMs`, so queries of server components
182
190
  * that render after the caller are counted, or after `maxMs` regardless.
@@ -184,8 +192,13 @@ declare module 'alinea/adapter/next/renderStats' {
184
192
  settled(quietMs?: number, maxMs?: number): Promise<PreviewStats>;
185
193
  }
186
194
  /**
187
- * Time the work a row stands for, such as answering a query without the sync
188
- * it waited for.
195
+ * A statement short enough to read in a table, such as
196
+ * `select … from alinea_entry_index where (visible = ? and id in (?))`.
197
+ */
198
+ export function summarizeSql(sql: string, maxLength?: number): string;
199
+ /**
200
+ * Time the work a row stands for from call to answer, such as a sync or a
201
+ * query the handler answers, without the sync it waited for.
189
202
  */
190
203
  export function timed<T>(row: PreviewStat | undefined, run: () => Promise<T>): Promise<T>;
191
204
  /** A short description such as `first(type, url=/about)`. */
@@ -422,9 +435,8 @@ declare module 'alinea/backend/api/OAuth2' {
422
435
 
423
436
  declare module 'alinea/backend/api/ProxyMedia' {
424
437
 
425
- import { type Request, Response } from '@alinea/iso';
426
- export function proxyMediaUrl(request: Request, source: URL, isAllowed?: (url: URL) => boolean): Promise<Response>;
427
- export function isRedirectStatus(status: number): boolean;
438
+ import { type Request } from '@alinea/iso';
439
+ export function proxyMediaUrl(request: Request, source: URL): Promise<Response>;
428
440
 
429
441
  }
430
442
 
@@ -562,7 +574,7 @@ declare module 'alinea/backend/HandleAction' {
562
574
  Mutate = "mutate",
563
575
  Upload = "upload",
564
576
  Tree = "tree",
565
- Commit = "commit",
577
+ Sha = "sha",
566
578
  Blob = "blob"
567
579
  }
568
580
 
@@ -806,10 +818,13 @@ declare module 'alinea/backend/router/Router' {
806
818
  declare module 'alinea/backend/store/GeneratedDatabase' {
807
819
 
808
820
  import type { Config } from 'alinea/core/Config';
821
+ import type { DatabaseHandle } from 'alinea/database/driver/DatabaseHandle';
809
822
  import { EntryStore } from 'alinea/database/EntryStore';
810
- import type { Database } from 'rado';
811
- /** Add one writable, connection-local layer over a generated database. */
812
- export function createGeneratedDatabase(config: Config, db: Database): Promise<EntryStore>;
823
+ /**
824
+ * Serve a generated database from a connection that keeps its writes in
825
+ * memory, so syncs and commits never reach the generated file.
826
+ */
827
+ export function createGeneratedDatabase(config: Config, { database: db, fork, driver }: DatabaseHandle): Promise<EntryStore>;
813
828
 
814
829
  }
815
830
 
@@ -817,7 +832,7 @@ declare module 'alinea/backend/store/GeneratedDatabaseNode' {
817
832
 
818
833
  import type { Config } from 'alinea/core/Config';
819
834
  import type { DatabaseOptions } from 'rado';
820
- /** Open the traced generated file through the native SQLite driver. */
835
+ /** Open the traced generated file as an in-memory overlay. */
821
836
  export function generatedDatabase(config: Config, options?: DatabaseOptions): Promise<import("alinea/database/EntryStore").EntryStore>;
822
837
 
823
838
  }
@@ -1080,6 +1095,7 @@ declare module 'alinea/cli/serve/CreateLocalServer' {
1080
1095
 
1081
1096
  declare module 'alinea/cli/serve/DevServer' {
1082
1097
 
1098
+ import type { CMS } from 'alinea/core/CMS';
1083
1099
  import { Config } from 'alinea/core/Config';
1084
1100
  import { Request, type Response } from '@alinea/iso';
1085
1101
  import type { BuildOptions } from 'esbuild';
@@ -1100,6 +1116,7 @@ declare module 'alinea/cli/serve/DevServer' {
1100
1116
  handle(input: Request): Promise<Response>;
1101
1117
  }
1102
1118
  export function createDevServer(dir: string, options: CreateDevServerOptions): Promise<DevServer>;
1119
+ export function forwardMutation(request: Request, cms: CMS, apiKey: string): Promise<Response | undefined>;
1103
1120
 
1104
1121
  }
1105
1122
 
@@ -2209,7 +2226,6 @@ declare module 'alinea/core/Client' {
2209
2226
  import { type AuthResult } from 'alinea/cloud/AuthResult';
2210
2227
  import type { Config } from 'alinea/core/Config';
2211
2228
  import type { BackendCapabilities, LocalConnection, Revision, UploadMetadata, UploadResponse } from 'alinea/core/Connection';
2212
- import type { CommitRequest } from 'alinea/core/db/CommitRequest';
2213
2229
  import type { Mutation } from 'alinea/core/db/Mutation';
2214
2230
  import type { Draft, DraftKey } from 'alinea/core/Draft';
2215
2231
  import type { EntryRecord } from 'alinea/core/EntryRecord';
@@ -2246,10 +2262,8 @@ declare module 'alinea/core/Client' {
2246
2262
  revisions(file: string): Promise<Array<Revision>>;
2247
2263
  revisionData(file: string, revisionId: string): Promise<EntryRecord | undefined>;
2248
2264
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
2265
+ getSha(): Promise<string>;
2249
2266
  getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
2250
- write(request: CommitRequest): Promise<{
2251
- sha: string;
2252
- }>;
2253
2267
  getDraft(key: DraftKey): Promise<Draft | undefined>;
2254
2268
  storeDraft(draft: Draft): Promise<void>;
2255
2269
  }
@@ -2383,7 +2397,8 @@ declare module 'alinea/core/Connection' {
2383
2397
  export interface BrowserConnection extends Connection {
2384
2398
  logout?(): Promise<void>;
2385
2399
  }
2386
- export interface LocalConnection extends Connection, CapabilitiesApi {
2400
+ /** A handler seen from the dashboard or an app: it commits by mutating. */
2401
+ export interface LocalConnection extends SyncApi, HistoryApi, DraftsApi, UploadsApi, UserApi, CapabilitiesApi {
2387
2402
  mutate(mutations: Array<Mutation>): Promise<{
2388
2403
  sha: string;
2389
2404
  }>;
@@ -2422,6 +2437,8 @@ declare module 'alinea/core/Connection' {
2422
2437
  applyAuth?(init?: RequestInit): RequestInit;
2423
2438
  }
2424
2439
  export const developmentKeyHeader = "x-alinea-dev-key";
2440
+ /** Marks a mutation an app sends back to the dev server after its hooks ran. */
2441
+ export const forwardedMutationHeader = "x-alinea-forwarded";
2425
2442
  export interface AuthedContext extends RequestContext {
2426
2443
  user: User;
2427
2444
  token: string;
@@ -2614,6 +2631,8 @@ declare module 'alinea/core/db/LocalStore' {
2614
2631
  */
2615
2632
  export interface LocalStore extends WriteableGraph, RemoteSource {
2616
2633
  source: Source;
2634
+ /** How the store reads its database, when it is a SQLite connection. */
2635
+ driver?: 'native' | 'overlay' | 'wasm';
2617
2636
  sha: string | Promise<string>;
2618
2637
  sync(): Promise<string>;
2619
2638
  syncWith(remote: RemoteSource, options?: SyncOptions): Promise<string>;
@@ -8038,7 +8057,7 @@ declare module 'alinea/dashboard/boot/LoadWorker' {
8038
8057
  declare module 'alinea/dashboard/boot/WorkerDB' {
8039
8058
 
8040
8059
  import type { Config } from 'alinea/core/Config';
8041
- import type { Connection, UploadMetadata, UploadResponse } from 'alinea/core/Connection';
8060
+ import type { LocalConnection, UploadMetadata, UploadResponse } from 'alinea/core/Connection';
8042
8061
  import type { AnyQueryResult, GraphQuery } from 'alinea/core/Graph';
8043
8062
  import type { EntryReferenceQuery, EntryReferenceResult } from 'alinea/core/db/EntryReference';
8044
8063
  import type { Mutation } from 'alinea/core/db/Mutation';
@@ -8049,7 +8068,7 @@ declare module 'alinea/dashboard/boot/WorkerDB' {
8049
8068
  #private;
8050
8069
  config: Config;
8051
8070
  events: EventTarget;
8052
- constructor(config: Config, worker: DashboardWorker, client: Connection, events: EventTarget);
8071
+ constructor(config: Config, worker: DashboardWorker, client: LocalConnection, events: EventTarget);
8053
8072
  mutate(mutations: Array<Mutation>): Promise<{
8054
8073
  id: string;
8055
8074
  sha: string;
@@ -8727,14 +8746,25 @@ declare module 'alinea/database/BrowserEntryStore' {
8727
8746
  import type { RemoteSource } from 'alinea/core/source/Source';
8728
8747
  import { EntryStore } from 'alinea/database/EntryStore';
8729
8748
  export interface BrowserEntryStoreOptions {
8730
- indexedDB: IDBFactory;
8731
8749
  name: string;
8732
8750
  revision: string;
8751
+ /**
8752
+ * The store this one replaces, such as one of a previous dashboard build:
8753
+ * its content carries over from memory instead of loading it again, and it
8754
+ * stops storing without waiting for its work in flight.
8755
+ */
8756
+ replaces?: BrowserEntryStore;
8757
+ /** The IndexedDB implementation, the global one by default. */
8758
+ indexedDB?: IDBFactory;
8759
+ IDBKeyRange?: typeof IDBKeyRange;
8733
8760
  }
8734
- /** WASM entry store persisted as one SQLite file in IndexedDB. */
8761
+ /**
8762
+ * WASM entry store kept in IndexedDB: every commit stores the pages it
8763
+ * changed.
8764
+ */
8735
8765
  export class BrowserEntryStore extends EntryStore {
8736
8766
  #private;
8737
- static open(config: Config, options: BrowserEntryStoreOptions): Promise<BrowserEntryStore>;
8767
+ static open(config: Config, options: BrowserEntryStoreOptions, retried?: boolean): Promise<BrowserEntryStore>;
8738
8768
  private constructor();
8739
8769
  sync(): Promise<string>;
8740
8770
  syncWith(remote: RemoteSource, options?: SyncOptions): Promise<string>;
@@ -8745,9 +8775,10 @@ declare module 'alinea/database/BrowserEntryStore' {
8745
8775
  sha: string;
8746
8776
  }>;
8747
8777
  close(): Promise<void>;
8748
- /** Close a superseded store without persisting stale bytes over its
8749
- * replacement's cache. Supersede always implies a revision change, so the
8750
- * persisted revision would be discarded on the next open anyway. */
8778
+ /**
8779
+ * Close a store that a replacement took over from: its work in flight
8780
+ * changes only its memory copy, which is not stored.
8781
+ */
8751
8782
  abandon(): Promise<void>;
8752
8783
  }
8753
8784
 
@@ -8758,11 +8789,11 @@ declare module 'alinea/database/DatabaseSource' {
8758
8789
  import type { ChangesBatch } from 'alinea/core/source/Change';
8759
8790
  import type { GetBlobsOptions, Source } from 'alinea/core/source/Source';
8760
8791
  import type { ReadonlyTree } from 'alinea/core/source/Tree';
8761
- import type { EntryLayer } from 'alinea/database/EntryLayer';
8792
+ import type { EntryDatabase } from 'alinea/database/EntryDatabase';
8762
8793
  /** Read-only Source view backed by exact payload text in an entry database. */
8763
8794
  export class DatabaseSource implements Source {
8764
8795
  #private;
8765
- constructor(database: EntryLayer);
8796
+ constructor(database: EntryDatabase);
8766
8797
  getTree(): Promise<ReadonlyTree>;
8767
8798
  getTreeIfDifferent(sha: string): Promise<ReadonlyTree | undefined>;
8768
8799
  getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array<ArrayBufferLike>], any, any>;
@@ -8814,28 +8845,89 @@ declare module 'alinea/database/DatabaseTables' {
8814
8845
 
8815
8846
  }
8816
8847
 
8848
+ declare module 'alinea/database/driver/DatabaseHandle' {
8849
+
8850
+ import type { Database } from 'rado';
8851
+ /**
8852
+ * How a connection reads its data: a native file, a native in-memory overlay
8853
+ * of a file, or a copy in WASM memory.
8854
+ */
8855
+ export type DatabaseDriver = 'native' | 'overlay' | 'wasm';
8856
+ /** A SQLite connection, which may copy itself without copying its data. */
8857
+ export interface DatabaseHandle {
8858
+ database: Database;
8859
+ driver?: DatabaseDriver;
8860
+ /**
8861
+ * An independent connection to the last committed state. The copy shares
8862
+ * every page with this one until either side writes it. Connections that
8863
+ * write a file in place cannot fork.
8864
+ */
8865
+ fork?(): Promise<DatabaseHandle>;
8866
+ }
8867
+
8868
+ }
8869
+
8870
+ declare module 'alinea/database/driver/OverlayExtension.browser' {
8871
+
8872
+ /** Browser bundles reach the database driver without ever running it. */
8873
+ export function overlayExtension(): string;
8874
+
8875
+ }
8876
+
8877
+ declare module 'alinea/database/driver/OverlayExtension' {
8878
+
8879
+ export { overlayExtension } from '@alinea/sqlite-wasm/native';
8880
+
8881
+ }
8882
+
8817
8883
  declare module 'alinea/database/driver/RuntimeDatabase' {
8818
8884
 
8819
- import type { Database, DatabaseOptions } from 'rado';
8885
+ import type { DatabaseOptions } from 'rado';
8886
+ import type { DatabaseHandle } from 'alinea/database/driver/DatabaseHandle';
8820
8887
  export interface RuntimeDatabaseOptions extends DatabaseOptions {
8821
- path?: string;
8822
- data?: Uint8Array;
8823
- readonly?: boolean;
8888
+ path: string;
8889
+ /** Keep every write in memory: the file is never written. */
8890
+ overlay?: boolean;
8824
8891
  }
8825
- /** Select a native Node/Bun driver for files and WASM for byte buffers. */
8826
- export function runtimeDatabase(options?: RuntimeDatabaseOptions): Promise<Database>;
8892
+ /**
8893
+ * Open a SQLite file with the native Node/Bun driver. Overlays of the file
8894
+ * keep their writes in memory and fork cheaply, through the native overlay
8895
+ * VFS or else a WASM copy; a file opened for writing cannot fork.
8896
+ */
8897
+ export function runtimeDatabase(options: RuntimeDatabaseOptions): Promise<DatabaseHandle>;
8827
8898
 
8828
8899
  }
8829
8900
 
8830
8901
  declare module 'alinea/database/driver/WasmDatabase' {
8831
8902
 
8903
+ import type { Storage } from '@alinea/sqlite-wasm/Database.js';
8904
+ import type { DatabaseOptions } from 'rado';
8832
8905
  import { connect } from 'rado/driver/sql.js';
8833
- export interface WasmDatabaseHandle {
8906
+ import type { DatabaseHandle } from 'alinea/database/driver/DatabaseHandle';
8907
+ export interface WasmDatabaseHandle extends DatabaseHandle {
8834
8908
  database: ReturnType<typeof connect>;
8909
+ fork(): Promise<WasmDatabaseHandle>;
8835
8910
  export(): Uint8Array;
8911
+ /** Resolves once every commit so far is stored; at once in memory. */
8912
+ flush(): Promise<void>;
8913
+ /**
8914
+ * Store this database from now on, replacing what the storage held, once
8915
+ * a database detached from it wrote its last commits.
8916
+ */
8917
+ attach(storage: Storage): Promise<void>;
8918
+ /**
8919
+ * Stop storing commits once the ones so far are written; later commits
8920
+ * change only the memory copy.
8921
+ */
8922
+ detach(): void;
8836
8923
  }
8924
+ /**
8925
+ * Open a WASM connection from the database kept in storage, or an empty one,
8926
+ * that stores every commit there.
8927
+ */
8928
+ export function syncWasmDatabase(storage: Storage, options?: DatabaseOptions): Promise<WasmDatabaseHandle>;
8837
8929
  /** Open an in-memory WASM connection, optionally from a complete SQLite file. */
8838
- export function openWasmDatabase(data?: Uint8Array): Promise<WasmDatabaseHandle>;
8930
+ export function openWasmDatabase(data?: Uint8Array, options?: DatabaseOptions): Promise<WasmDatabaseHandle>;
8839
8931
  export function wasmDatabase(data?: Uint8Array): Promise<import("rado").SyncDatabase<"sqlite">>;
8840
8932
 
8841
8933
  }
@@ -8860,13 +8952,14 @@ declare module 'alinea/database/entry/EntryData' {
8860
8952
 
8861
8953
  declare module 'alinea/database/entry/EntryTable' {
8862
8954
 
8955
+ import type { Config } from 'alinea/core/Config';
8863
8956
  import type { Entry, EntryStatus } from 'alinea/core/Entry';
8864
- import { type Table } from 'rado';
8957
+ import { type Database, type Table } from 'rado';
8865
8958
  /** One complete authored entry version. Only arrays and authored data are JSON. */
8866
8959
  export const EntryIndexColumns: {
8867
8960
  /**
8868
8961
  * Also the rowid of the version's full-text search row. Declared, so VACUUM
8869
- * and copies into overlay tables keep it.
8962
+ * keeps it.
8870
8963
  */
8871
8964
  rowid: import("rado").Column<number, [false, false]>;
8872
8965
  versionId: import("rado").Column<string, [false, boolean]>;
@@ -8905,10 +8998,10 @@ declare module 'alinea/database/entry/EntryTable' {
8905
8998
  */
8906
8999
  data: import("rado").Column<string, [false, boolean]>;
8907
9000
  };
8908
- export function entryIndexTable(name: string, temporary?: boolean): Table<{
9001
+ export function entryIndexTable(name: string): Table<{
8909
9002
  /**
8910
9003
  * Also the rowid of the version's full-text search row. Declared, so VACUUM
8911
- * and copies into overlay tables keep it.
9004
+ * keeps it.
8912
9005
  */
8913
9006
  rowid: import("rado").Column<number, [false, false]>;
8914
9007
  versionId: import("rado").Column<string, [false, boolean]>;
@@ -8951,7 +9044,7 @@ declare module 'alinea/database/entry/EntryTable' {
8951
9044
  export const EntryIndexTable: Table<{
8952
9045
  /**
8953
9046
  * Also the rowid of the version's full-text search row. Declared, so VACUUM
8954
- * and copies into overlay tables keep it.
9047
+ * keeps it.
8955
9048
  */
8956
9049
  rowid: import("rado").Column<number, [false, false]>;
8957
9050
  versionId: import("rado").Column<string, [false, boolean]>;
@@ -8990,6 +9083,21 @@ declare module 'alinea/database/entry/EntryTable' {
8990
9083
  */
8991
9084
  data: import("rado").Column<string, [false, boolean]>;
8992
9085
  }, string>;
9086
+ /** The entry ids each entry version references, under the version's rowid. */
9087
+ export const EntryReferenceTable: Table<{
9088
+ targetId: import("rado").Column<string, [false, boolean]>;
9089
+ source: import("rado").Column<number, [false, boolean]>;
9090
+ }, "alinea_entry_reference">;
9091
+ /** Fields whose queries order by their stored value, through a field index. */
9092
+ export function isOrderedField(field: unknown): boolean;
9093
+ /** Metadata fields whose queries order by their stored value. */
9094
+ export const orderedMetadata: Set<string>;
9095
+ /**
9096
+ * Index the stored value of every ordered field in the config, per type, and
9097
+ * of the ordered metadata. Field names are identifiers, so the dotted metadata
9098
+ * names cannot collide with them.
9099
+ */
9100
+ export function syncFieldIndexes(db: Database, config: Config): Promise<void>;
8993
9101
  /** An Entry plus the physical-version and local-index fields. */
8994
9102
  export interface IndexedEntry extends Entry {
8995
9103
  versionStatus: EntryStatus;
@@ -9036,93 +9144,29 @@ declare module 'alinea/database/entry/EntryTable' {
9036
9144
 
9037
9145
  }
9038
9146
 
9039
- declare module 'alinea/database/entry/EntryView' {
9040
-
9041
- import { type Database } from 'rado';
9042
- import type { EntrySyncTarget } from 'alinea/database/sync/EntrySyncer';
9043
- /** The tables reads of a target go to: an unwritten view reads its parent's. */
9044
- export function entryReadTarget(target: EntrySyncTarget): EntrySyncTarget;
9045
- /**
9046
- * Named, copy-on-write entry and search tables over another target. A view
9047
- * reads its parent until the first write copies the parent's rows into
9048
- * temporary tables of its own, which from then on no longer follow the parent.
9049
- */
9050
- export class EntryView {
9051
- #private;
9052
- readonly target: EntrySyncTarget;
9053
- private constructor();
9054
- static create(db: Database, name: string, parent: EntrySyncTarget, revision: string): Promise<EntryView>;
9055
- get readTarget(): EntrySyncTarget;
9056
- /**
9057
- * Copy the parent's rows into this view's tables. Await this before every
9058
- * write through the view: until then reads go to the parent.
9059
- */
9060
- diverge(): Promise<void>;
9061
- close(): Promise<void>;
9062
- }
9063
-
9064
- }
9065
-
9066
9147
  declare module 'alinea/database/EntryDatabase' {
9067
9148
 
9068
- import type { Config } from 'alinea/core/Config';
9069
- import type { FileStat } from 'alinea/core/source/FSSource';
9070
- import { type Database } from 'rado';
9071
- import { EntryLayer, type EntryDatabaseOptions, type EntrySyncResult } from 'alinea/database/EntryLayer';
9072
- /** A queryable entry database owning its SQLite connection. */
9073
- export class EntryDatabase extends EntryLayer {
9074
- #private;
9075
- constructor(config: Config, db: Database, options?: EntryDatabaseOptions);
9076
- static createSchema(db: Database, revision: string, configFingerprint?: string): Promise<void>;
9077
- /**
9078
- * Derive every entry again with another config, recording the fingerprint
9079
- * so a later open recognizes the database as written by that config.
9080
- */
9081
- reindex(config: Config, configFingerprint?: string): Promise<EntrySyncResult>;
9082
- /** File stats recorded by the last filesystem sync of this database. */
9083
- getSourceFileStats(): Promise<Map<string, FileStat>>;
9084
- /** Replace the recorded file stats after syncing from a filesystem source. */
9085
- setSourceFileStats(stats: ReadonlyMap<string, FileStat>): Promise<void>;
9086
- compact(): Promise<void>;
9087
- protected releaseLayer(): Promise<void>;
9088
- }
9089
-
9090
- }
9091
-
9092
- declare module 'alinea/database/EntryLayer' {
9093
-
9094
9149
  import type { Config } from 'alinea/core/Config';
9095
9150
  import type { CommitRequest } from 'alinea/core/db/CommitRequest';
9096
9151
  import type { EntryReferenceQuery, EntryReferenceResult } from 'alinea/core/db/EntryReference';
9097
- import type { Mutation } from 'alinea/core/db/Mutation';
9098
9152
  import type { SyncOptions } from 'alinea/core/db/LocalStore';
9153
+ import type { Mutation } from 'alinea/core/db/Mutation';
9099
9154
  import { Graph, type AnyQueryResult, type GraphQuery } from 'alinea/core/Graph';
9100
9155
  import { Policy } from 'alinea/core/Role';
9101
- import { OverlaySource } from 'alinea/core/source/OverlaySource';
9156
+ import type { FileStat } from 'alinea/core/source/FSSource';
9102
9157
  import { type GetBlobsOptions, type RemoteSource, type Source } from 'alinea/core/source/Source';
9103
9158
  import { ReadonlyTree } from 'alinea/core/source/Tree';
9104
- import { TaskQueue } from 'alinea/core/util/Async';
9105
9159
  import { type Database } from 'rado';
9106
- import { EntryView } from 'alinea/database/entry/EntryView';
9107
- import { EntrySyncer, type EntrySyncTarget } from 'alinea/database/sync/EntrySyncer';
9108
- export interface EntryLayerState {
9109
- /** Read connection, or the write transaction of a working layer. */
9110
- db: Database;
9111
- /** Serializes every task on the connection, shared by its overlays. */
9112
- queue: TaskQueue;
9113
- syncer: EntrySyncer;
9114
- target: EntrySyncTarget;
9115
- /** The copy-on-write table behind an overlay's target. */
9116
- view?: EntryView;
9117
- /** The layer an overlay was created from. */
9118
- parent?: EntryLayer;
9119
- tree?: ReadonlyTree;
9120
- initialTree?: ReadonlyTree;
9121
- /** The connection is already inside a transaction: never open nested ones. */
9122
- transactional?: boolean;
9123
- }
9160
+ import type { DatabaseHandle } from 'alinea/database/driver/DatabaseHandle';
9124
9161
  export interface EntryDatabaseOptions {
9125
9162
  includedAtBuild?(filePath: string): boolean | Promise<boolean>;
9163
+ /** Copy the connection for an overlay; without it there are none. */
9164
+ fork?(): Promise<DatabaseHandle>;
9165
+ /**
9166
+ * Whether syncs record the source tree in the state, so the database can be
9167
+ * reopened and synced from it. An overlay keeps its tree in memory.
9168
+ */
9169
+ recordsTree?: boolean;
9126
9170
  }
9127
9171
  export interface EntryApplyOptions {
9128
9172
  source: Source;
@@ -9137,52 +9181,67 @@ declare module 'alinea/database/EntryLayer' {
9137
9181
  export interface EntryApplyResult extends EntrySyncResult {
9138
9182
  request: CommitRequest;
9139
9183
  }
9140
- export interface EntryDatabaseOverlay extends AsyncDisposable {
9141
- database: EntryLayer;
9142
- source: OverlaySource;
9143
- close(): Promise<void>;
9144
- }
9145
- /** One queryable layer of the entry index: a database or a view over one. */
9146
- export class EntryLayer extends Graph implements AsyncDisposable {
9184
+ /**
9185
+ * A queryable entry index owning its SQLite connection. Mutations are planned
9186
+ * on a working copy inside a write transaction of that connection.
9187
+ */
9188
+ export class EntryDatabase extends Graph implements AsyncDisposable {
9147
9189
  #private;
9148
- constructor(config: Config, options: EntryDatabaseOptions, state: EntryLayerState);
9190
+ constructor(config: Config, db: Database, options?: EntryDatabaseOptions);
9149
9191
  get config(): Config;
9192
+ /** Whether this database can create overlays. */
9193
+ get forkable(): boolean;
9150
9194
  includedAtBuild(filePath: string): boolean | Promise<boolean>;
9151
- /** Whether this layer was closed, and answers only from a replacement. */
9195
+ /** Whether this database was closed, and answers only from a replacement. */
9152
9196
  get closed(): boolean;
9153
9197
  /**
9154
- * Synchronize a source through this layer's single prepared syncer, without
9155
- * keeping an in-memory copy of its entries.
9198
+ * Run a task while no other task uses the connection, so no transaction
9199
+ * is open: a fork of the connection then copies committed state.
9200
+ */
9201
+ whileIdle<T>(task: () => Promise<T>): Promise<T>;
9202
+ /**
9203
+ * Fork the connection and synchronize the copy with a source. The overlay
9204
+ * shares every unchanged page with this database and is independent of it:
9205
+ * either can change or close without affecting the other.
9206
+ */
9207
+ overlay(source: RemoteSource): Promise<EntryDatabase>;
9208
+ static createSchema(db: Database, config: Config, revision: string, configFingerprint?: string): Promise<void>;
9209
+ /**
9210
+ * Synchronize a source through this database's single prepared syncer,
9211
+ * without keeping an in-memory copy of its entries.
9156
9212
  */
9157
9213
  syncWith(source: RemoteSource, options?: SyncOptions): Promise<EntrySyncResult>;
9158
9214
  /**
9159
9215
  * Adopt another config and derive every entry again from the payloads this
9160
- * layer stores. The rows of the previous config are replaced within one
9161
- * transaction, so reads keep answering from them until the replacement is
9162
- * committed. Only a layer without overlays can be reindexed.
9216
+ * database stores, recording the fingerprint so a later open recognizes the
9217
+ * database as written by that config. The rows of the previous config are
9218
+ * replaced within one transaction, so reads keep answering from them until
9219
+ * the replacement is committed.
9163
9220
  */
9164
- protected reindexEntries(config: Config, reset?: (tx: Database) => Promise<void>): Promise<EntrySyncResult>;
9165
- /** Atomically plan and apply mutations through a private working layer. */
9221
+ reindex(config: Config, configFingerprint?: string): Promise<EntrySyncResult>;
9222
+ /** Atomically plan and apply mutations through a private working copy. */
9166
9223
  apply(mutations: ReadonlyArray<Mutation>, options: EntryApplyOptions): Promise<EntryApplyResult>;
9167
9224
  /** Plan mutations like apply, then roll them back. */
9168
9225
  plan(mutations: ReadonlyArray<Mutation>, options: EntryApplyOptions): Promise<CommitRequest>;
9226
+ /**
9227
+ * Resolve a query with a source synced into this database, within a write
9228
+ * transaction that is rolled back afterwards.
9229
+ */
9230
+ resolveWith<const Query extends GraphQuery>(source: RemoteSource, query: Query): Promise<AnyQueryResult<Query>>;
9169
9231
  close(): Promise<void>;
9170
- /** Release what this layer owns, serialized with the read connection. */
9171
- protected releaseLayer(): Promise<void>;
9172
9232
  [Symbol.asyncDispose](): Promise<void>;
9173
9233
  getRevision(): Promise<string>;
9174
9234
  getTree(): Promise<ReadonlyTree>;
9175
9235
  getBlobs(shas: ReadonlyArray<string>, options?: GetBlobsOptions): AsyncGenerator<[sha: string, blob: Uint8Array]>;
9176
- /** Run a task serialized with every other read on this connection. */
9177
- protected withReadConnection<T>(run: () => Promise<T>): Promise<T>;
9178
- /** Create and synchronize a copy-on-write layer over this one. */
9179
- overlay(source: RemoteSource): Promise<EntryLayer>;
9180
- /** Create an initially empty writable source and database layer. */
9181
- createOverlay(): Promise<EntryDatabaseOverlay>;
9182
9236
  resolve<const Query extends GraphQuery>(query: Query): Promise<AnyQueryResult<Query>>;
9183
9237
  /** Scan references in bounded pages without retaining an entry index. */
9184
9238
  referencesTo(query: EntryReferenceQuery): Promise<EntryReferenceResult>;
9185
9239
  onChange(listener: EntryChangeListener): () => void;
9240
+ /** File stats recorded by the last filesystem sync of this database. */
9241
+ getSourceFileStats(): Promise<Map<string, FileStat>>;
9242
+ /** Replace the recorded file stats after syncing from a filesystem source. */
9243
+ setSourceFileStats(stats: ReadonlyMap<string, FileStat>): Promise<void>;
9244
+ compact(): Promise<void>;
9186
9245
  }
9187
9246
 
9188
9247
  }
@@ -9200,39 +9259,37 @@ declare module 'alinea/database/EntryStore' {
9200
9259
  import type { LocalStore, SyncOptions } from 'alinea/core/db/LocalStore';
9201
9260
  import { WriteableGraph } from 'alinea/core/db/WriteableGraph';
9202
9261
  import type { UploadMetadata, UploadResponse } from 'alinea/core/Connection';
9203
- import type { EntryChangeListener, EntryLayer } from 'alinea/database/EntryLayer';
9262
+ import { EntryDatabase, type EntryChangeListener } from 'alinea/database/EntryDatabase';
9263
+ import type { DatabaseDriver } from 'alinea/database/driver/DatabaseHandle';
9204
9264
  export interface EntryStoreOptions {
9205
9265
  ownsDatabase?: boolean;
9206
9266
  sourceFollowsDatabase?: boolean;
9207
- close?: () => Promise<void>;
9267
+ driver?: DatabaseDriver;
9208
9268
  }
9209
9269
  /** Source and commit lifecycle around the transport-neutral SQLite database. */
9210
9270
  export class EntryStore extends WriteableGraph implements LocalStore, AsyncDisposable {
9211
9271
  #private;
9212
9272
  /** Replaced only by a reindex, which derives every entry again. */
9213
9273
  config: Config;
9214
- readonly database: EntryLayer;
9274
+ readonly database: EntryDatabase;
9215
9275
  readonly source: Source;
9216
- constructor(config: Config, database: EntryLayer, source: Source, options?: EntryStoreOptions);
9276
+ readonly driver?: DatabaseDriver;
9277
+ constructor(config: Config, database: EntryDatabase, source: Source, options?: EntryStoreOptions);
9217
9278
  static memory(config: Config, source: Source): Promise<EntryStore>;
9218
9279
  get sha(): Promise<string>;
9219
9280
  includedAtBuild(filePath: string): boolean | Promise<boolean>;
9220
9281
  /**
9221
- * Preview queries share a single overlay, which each query first brings to
9222
- * its own payload over the current store revision. The overlay diffs from
9223
- * the tree it shows, so a switch restores the previously previewed entry and
9224
- * follows store syncs without copying the entries again.
9282
+ * Preview queries resolve over the store with the previewed entry synced
9283
+ * in. The last payload keeps its fork, so repeated queries of one preview
9284
+ * sync once; another payload or store revision forks afresh. A database
9285
+ * that cannot fork syncs each preview in a transaction it rolls back.
9225
9286
  */
9226
9287
  resolve<Query extends GraphQuery>(query: Query): Promise<AnyQueryResult<Query>>;
9227
- /** Close the preview overlay, which keeps this store's database open. */
9228
- protected closePreviews(): Promise<void>;
9229
9288
  referencesTo(query: EntryReferenceQuery): Promise<EntryReferenceResult>;
9230
9289
  onChange(listener: EntryChangeListener): () => void;
9231
9290
  sync(): Promise<string>;
9232
9291
  /** Keep the writable source and its query database at one remote revision. */
9233
9292
  syncWith(remote: RemoteSource, options?: SyncOptions): Promise<string>;
9234
- /** Create a persistent copy-on-write session over this store. */
9235
- overlay(remote: RemoteSource): Promise<EntryStore>;
9236
9293
  /** Plan a commit without changing this store. */
9237
9294
  request(mutations: ReadonlyArray<Mutation>, policy?: Policy): Promise<CommitRequest>;
9238
9295
  mutate(mutations: Array<Mutation>): Promise<{
@@ -9260,7 +9317,7 @@ declare module 'alinea/database/EntryTransaction' {
9260
9317
  import type { ReadonlyTree } from 'alinea/core/source/Tree';
9261
9318
  import { type CommitRequest } from 'alinea/core/db/CommitRequest';
9262
9319
  import type { ArchiveMutation, CreateMutation, MoveMutation, Mutation, PublishMutation, RemoveFileMutation, RemoveMutation, UnpublishMutation, UpdateMutation, UploadFileMutation } from 'alinea/core/db/Mutation';
9263
- import type { EntryLayer } from 'alinea/database/EntryLayer';
9320
+ import type { EntryDatabase } from 'alinea/database/EntryDatabase';
9264
9321
  type Op<T> = Omit<T, 'op'>;
9265
9322
  /**
9266
9323
  * Plans mutations inside the receiver's write transaction. Each mutation is
@@ -9269,8 +9326,8 @@ declare module 'alinea/database/EntryTransaction' {
9269
9326
  */
9270
9327
  export class EntryTransaction implements AsyncDisposable {
9271
9328
  #private;
9272
- /** @internal Constructed by EntryLayer.apply. */
9273
- constructor(workingDatabase: EntryLayer, workingSource: OverlaySource, sourceTransaction: SourceTransaction, from: ReadonlyTree, policy: Policy);
9329
+ /** @internal Constructed by EntryDatabase.apply. */
9330
+ constructor(workingDatabase: EntryDatabase, workingSource: OverlaySource, sourceTransaction: SourceTransaction, from: ReadonlyTree, policy: Policy);
9274
9331
  get changedEntryIds(): Array<string>;
9275
9332
  apply(mutations: ReadonlyArray<Mutation>): Promise<void>;
9276
9333
  create({ locale, type, data, root, workspace, fromSeed, parentId, id, insertOrder, status, overwrite }: Op<CreateMutation>): Promise<void>;
@@ -9362,7 +9419,7 @@ declare module 'alinea/database/query/EntryQuery' {
9362
9419
  import { type Scope } from 'alinea/core/Scope';
9363
9420
  import { type HasSql, type SelectionInput, type Sql } from 'rado';
9364
9421
  import { type EntryIndexTarget } from 'alinea/database/entry/EntryTable';
9365
- import { searchQuery, type EntrySearchTarget } from 'alinea/database/query/Search';
9422
+ import { searchQuery } from 'alinea/database/query/Search';
9366
9423
  interface RelationProjection {
9367
9424
  path: Array<string>;
9368
9425
  plan: ProjectionPlan;
@@ -9392,7 +9449,8 @@ declare module 'alinea/database/query/EntryQuery' {
9392
9449
  entry?: EntryIndexTarget;
9393
9450
  depth?: number;
9394
9451
  baseEntry?: EntryIndexTarget;
9395
- searchTable?: EntrySearchTarget;
9452
+ /** Wrap every row and carry its entry id next to the selection. */
9453
+ withId?: boolean;
9396
9454
  }
9397
9455
  export function compileEntryQuery(config: Config, query: GraphQuery, options?: EntryQueryOptions): {
9398
9456
  rows: import("rado").Select<SelectionInput, import("rado/core/MetaData").QueryMeta>;
@@ -9407,9 +9465,8 @@ declare module 'alinea/database/query/EntryReferences' {
9407
9465
  import type { Config } from 'alinea/core/Config';
9408
9466
  import type { EntryReferenceQuery, EntryReferenceResult } from 'alinea/core/db/EntryReference';
9409
9467
  import { type Database } from 'rado';
9410
- import { type EntryIndexTarget } from 'alinea/database/entry/EntryTable';
9411
- /** Scan references in bounded pages on the caller's active transaction. */
9412
- export function queryEntryReferences(config: Config, db: Database, entry: EntryIndexTarget, query: EntryReferenceQuery): Promise<EntryReferenceResult>;
9468
+ /** Read the versions indexed as referencing the target and walk their data. */
9469
+ export function queryEntryReferences(config: Config, db: Database, query: EntryReferenceQuery): Promise<EntryReferenceResult>;
9413
9470
 
9414
9471
  }
9415
9472
 
@@ -9434,14 +9491,11 @@ declare module 'alinea/database/query/ResolveQuery' {
9434
9491
  import type { Config } from 'alinea/core/Config';
9435
9492
  import type { GraphQuery } from 'alinea/core/Graph';
9436
9493
  import { type Database } from 'rado';
9437
- import type { EntryIndexTarget } from 'alinea/database/entry/EntryTable';
9438
- import type { EntrySearchTarget, SearchQuery } from 'alinea/database/query/Search';
9494
+ import type { SearchQuery } from 'alinea/database/query/Search';
9439
9495
  /** Query dependencies bound to one connection and its current transaction. */
9440
9496
  export interface EntryQueryContext {
9441
9497
  config: Config;
9442
9498
  database: Database;
9443
- entries: EntryIndexTarget;
9444
- searchTable: EntrySearchTarget;
9445
9499
  search(input: GraphQuery['search']): Promise<SearchQuery | undefined>;
9446
9500
  includedAtBuild(filePath: string): boolean | Promise<boolean>;
9447
9501
  }
@@ -9451,20 +9505,13 @@ declare module 'alinea/database/query/ResolveQuery' {
9451
9505
 
9452
9506
  declare module 'alinea/database/query/Search' {
9453
9507
 
9454
- import { type Database, type HasSql, type Sql, type Table } from 'rado';
9508
+ import { type Database, type HasSql, type Sql } from 'rado';
9455
9509
  import type { EntryIndexTarget } from 'alinea/database/entry/EntryTable';
9456
- /**
9457
- * The FTS5 index of an entry table. Sync writes a row for every entry version
9458
- * under the rowid of its entry row; the body is the version's searchable text.
9459
- */
9460
- const EntrySearchColumns: {
9510
+ export const EntrySearchTable: import("rado").Table<{
9461
9511
  rowid: import("rado").Column<number, [false, false]>;
9462
9512
  title: import("rado").Column<string, [false, boolean]>;
9463
9513
  body: import("rado").Column<string, [false, boolean]>;
9464
- };
9465
- export type EntrySearchTarget = Table<typeof EntrySearchColumns>;
9466
- export function entrySearchTable(name: string, temporary?: boolean): EntrySearchTarget;
9467
- export const EntrySearchTable: EntrySearchTarget;
9514
+ }, "alinea_entry_search">;
9468
9515
  export interface SearchQuery {
9469
9516
  target: Sql;
9470
9517
  identity: Sql<boolean>;
@@ -9472,32 +9519,30 @@ declare module 'alinea/database/query/Search' {
9472
9519
  rank: Sql<number>;
9473
9520
  snippet(start: HasSql, end: HasSql, cutOff: HasSql, limit: HasSql): Sql<string>;
9474
9521
  }
9475
- export function createSearch(db: Database, search: EntrySearchTarget): Promise<void>;
9522
+ export function createSearch(db: Database): Promise<void>;
9476
9523
  /** The searchable text of an entry row, read from its search row. */
9477
- export function searchableText(entry: EntryIndexTarget, search: EntrySearchTarget): Sql<string>;
9524
+ export function searchableText(entry: EntryIndexTarget): Sql<string>;
9478
9525
  export function searchTokens(input: string | Array<string> | undefined): RegExpMatchArray | [] | undefined;
9479
9526
  export interface SearchQueryOptions {
9480
9527
  /** Indexed spellings a token may also match, besides its prefix. */
9481
9528
  alternatives?(token: string): ReadonlyArray<string>;
9482
9529
  }
9483
- export function searchQuery(input: string | Array<string> | undefined, entry: EntryIndexTarget, target: EntrySearchTarget, options?: SearchQueryOptions): SearchQuery | undefined;
9530
+ export function searchQuery(input: string | Array<string> | undefined, entry: EntryIndexTarget, options?: SearchQueryOptions): SearchQuery | undefined;
9484
9531
  /** The edits a token may be away from an indexed term: none below five
9485
9532
  * characters, one up to fourteen, then one more per ten. */
9486
9533
  export function fuzzyDistance(token: string): number;
9487
- export function dropVocabulary(db: Database, search: EntrySearchTarget): Promise<void>;
9488
9534
  /**
9489
- * The terms of one FTS5 table, loaded once per database revision. Query tokens
9535
+ * The terms of the search index, loaded once per database revision. Query tokens
9490
9536
  * expand to indexed terms within a small edit distance, so a typo still
9491
9537
  * finds the entry.
9492
9538
  */
9493
9539
  export class SearchVocabulary {
9494
9540
  #private;
9495
- load(db: Database, search: EntrySearchTarget, revision: string): Promise<void>;
9541
+ load(db: Database, revision: string): Promise<void>;
9496
9542
  /** Indexed terms close to a token, most frequent first; prefixes of the
9497
9543
  * token are left to the prefix match. */
9498
9544
  alternatives(token: string): Array<string>;
9499
9545
  }
9500
- export {};
9501
9546
 
9502
9547
  }
9503
9548
 
@@ -9518,8 +9563,12 @@ declare module 'alinea/database/sync/EntryParser' {
9518
9563
 
9519
9564
  import type { Config } from 'alinea/core/Config';
9520
9565
  import type { IndexedEntry } from 'alinea/database/entry/EntryTable';
9566
+ /** A parsed source entry and the ids of the entries it references. */
9567
+ export interface ParsedEntry extends IndexedEntry {
9568
+ references: Array<string>;
9569
+ }
9521
9570
  /** Parse one source blob into the entry fields that do not depend on its tree. */
9522
- export function parseSourceEntry(config: Config, filePath: string, fileHash: string, blob: Uint8Array): IndexedEntry;
9571
+ export function parseSourceEntry(config: Config, filePath: string, fileHash: string, blob: Uint8Array): ParsedEntry;
9523
9572
 
9524
9573
  }
9525
9574
 
@@ -9528,27 +9577,17 @@ declare module 'alinea/database/sync/EntrySyncer' {
9528
9577
  import type { Config } from 'alinea/core/Config';
9529
9578
  import type { RemoteSource } from 'alinea/core/source/Source';
9530
9579
  import { ReadonlyTree } from 'alinea/core/source/Tree';
9531
- import type { Database, Table } from 'rado';
9532
- import { type DatabaseStateColumns } from 'alinea/database/DatabaseTables';
9533
- import { type EntryIndexTarget } from 'alinea/database/entry/EntryTable';
9534
- import { type EntrySearchTarget } from 'alinea/database/query/Search';
9535
- export interface EntrySyncTarget {
9536
- entries: EntryIndexTarget;
9537
- state: Table<typeof DatabaseStateColumns>;
9538
- /** The full-text index of the entries, written along with them. */
9539
- search: EntrySearchTarget;
9540
- /**
9541
- * Whether the state records the synced source tree, so the database can be
9542
- * reopened and synced from it. A temporary overlay keeps its tree in memory.
9543
- */
9544
- recordsTree?: boolean;
9545
- }
9546
- export const EntrySyncRoot: EntrySyncTarget;
9580
+ import type { Database } from 'rado';
9547
9581
  export interface EntrySyncOptions {
9548
9582
  previousTree?: ReadonlyTree;
9549
9583
  withinTransaction?: boolean;
9550
9584
  /** Skip entry validation for a source whose entries were already validated. */
9551
9585
  validate?: boolean;
9586
+ /**
9587
+ * Record the synced source tree in the state, so the database can be
9588
+ * reopened and synced from it. Defaults to true.
9589
+ */
9590
+ recordsTree?: boolean;
9552
9591
  }
9553
9592
  /** Prepared, serialized source synchronization for one database connection. */
9554
9593
  export class EntrySyncer {
@@ -9557,9 +9596,7 @@ declare module 'alinea/database/sync/EntrySyncer' {
9557
9596
  /** Derive later syncs with another config; prepared statements are shared. */
9558
9597
  reconfigure(config: Config): Promise<void>;
9559
9598
  /** Stream a source/tree diff directly into the canonical SQLite table. */
9560
- sync(target: EntrySyncTarget, source: RemoteSource, tree: ReadonlyTree, fromRevision: string, options?: EntrySyncOptions): Promise<Array<string>>;
9561
- /** Release prepared statements belonging to a closed named target. */
9562
- release(target: EntrySyncTarget): Promise<void>;
9599
+ sync(source: RemoteSource, tree: ReadonlyTree, fromRevision: string, options?: EntrySyncOptions): Promise<Array<string>>;
9563
9600
  close(): Promise<void>;
9564
9601
  }
9565
9602
 
@@ -9597,7 +9634,6 @@ declare module 'alinea/database/sync/SyncQueries' {
9597
9634
  import type { Tree } from 'alinea/core/source/Tree';
9598
9635
  import { type Database } from 'rado';
9599
9636
  import { type entryIndexRow } from 'alinea/database/entry/EntryTable';
9600
- import type { EntrySyncTarget } from 'alinea/database/sync/EntrySyncer';
9601
9637
  export const sqliteBatchSize = 5000;
9602
9638
  /**
9603
9639
  * Named parameters bypass column encoders, so bind SQLite values explicitly for
@@ -9633,7 +9669,7 @@ declare module 'alinea/database/sync/SyncQueries' {
9633
9669
  main: number;
9634
9670
  visible: number;
9635
9671
  };
9636
- export function prepareSyncQueries(db: Database, target: EntrySyncTarget,
9672
+ export function prepareSyncQueries(db: Database,
9637
9673
  /** Store entry data as JSONB, on a SQLite that reads it. */
9638
9674
  jsonb: boolean): {
9639
9675
  revision: import("rado").PreparedQuery<{
@@ -9649,6 +9685,8 @@ declare module 'alinea/database/sync/SyncQueries' {
9649
9685
  insertEntry: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9650
9686
  /** Index the entry row inserted last, under its rowid. */
9651
9687
  insertSearch: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9688
+ /** Record the entry ids a version references, under its rowid. */
9689
+ insertReferences: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9652
9690
  /** Versions stored at these file paths or under these version ids. */
9653
9691
  storedFiles: import("rado").PreparedQuery<{
9654
9692
  id: string;
@@ -9660,6 +9698,8 @@ declare module 'alinea/database/sync/SyncQueries' {
9660
9698
  deleteFiles: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9661
9699
  /** Delete the search rows of those versions, before the versions. */
9662
9700
  deleteSearch: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9701
+ /** Delete the reference rows of those versions, before the versions. */
9702
+ deleteReferences: import("rado").PreparedQuery<import("rado/core/MetaData").MutationResultBase, Record<string, unknown>, import("rado/core/MetaData").QueryMeta>;
9663
9703
  directories: import("rado").PreparedQuery<{
9664
9704
  id: string;
9665
9705
  childrenDir: string;
@@ -9761,8 +9801,8 @@ declare module 'alinea/database/Version' {
9761
9801
  * by this version rather than the package version, so releases that keep the
9762
9802
  * schema reuse the databases that are already there.
9763
9803
  */
9764
- export const databaseVersion = 5;
9765
- export const generatedDatabaseFile = "database-v5.sqlite";
9804
+ export const databaseVersion = 8;
9805
+ export const generatedDatabaseFile = "database-v8.sqlite";
9766
9806
  export function versionedCacheName(name: string): string;
9767
9807
 
9768
9808
  }
@@ -11967,7 +12007,32 @@ declare module 'alinea/preview/widget' {
11967
12007
  summary: string;
11968
12008
  /** Whether the query was answered by the bundled database or the handler. */
11969
12009
  source?: 'database' | 'handler';
12010
+ /**
12011
+ * Execution time for queries the bundled database answers, without the
12012
+ * wait behind the render's other queries; time from call to answer for
12013
+ * syncs and queries the handler answers.
12014
+ */
12015
+ durationMs: number;
12016
+ /** SQL statements this row's work ran, such as a preview's sync. */
12017
+ statements?: number;
12018
+ sqlMs?: number;
12019
+ }
12020
+ /** One of the slowest SQL statements of a render. */
12021
+ export interface PreviewStatement {
12022
+ /** The statement, shortened */
12023
+ sql: string;
11970
12024
  durationMs: number;
12025
+ /** Summary of the query or sync that ran it */
12026
+ query?: string;
12027
+ }
12028
+ /** The bundled database that answered the render's queries. */
12029
+ export interface PreviewDatabase {
12030
+ /** The native overlay, the native file, or the WASM copy */
12031
+ driver?: string;
12032
+ /** Time this process took to open it */
12033
+ openMs: number;
12034
+ /** Whether this render waited for it to open */
12035
+ cold: boolean;
11971
12036
  }
11972
12037
  /** The CMS work of one render, shown in the widget. */
11973
12038
  export interface PreviewStats {
@@ -11975,6 +12040,10 @@ declare module 'alinea/preview/widget' {
11975
12040
  /** SQL statements run in this process for the render. */
11976
12041
  statements: number;
11977
12042
  sqlMs: number;
12043
+ /** Time from the first query or sync of the render until the last settled. */
12044
+ renderMs: number;
12045
+ slowest?: Array<PreviewStatement>;
12046
+ database?: PreviewDatabase;
11978
12047
  }
11979
12048
  export function registerPreviewWidget(): void;
11980
12049
  global {