alinea 2.0.0-preview.0 → 2.0.0-preview.2

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 (237) hide show
  1. package/dist/.init/content/pages/welcome.json +1 -1
  2. package/dist/LICENSES.md +134 -80
  3. package/dist/adapter/next/DevRemote.d.ts +3 -0
  4. package/dist/adapter/next/DevRemote.js +37 -0
  5. package/dist/adapter/next/ForwardCredentials.d.ts +1 -0
  6. package/dist/adapter/next/ForwardCredentials.js +17 -0
  7. package/dist/adapter/next/cms.js +86 -38
  8. package/dist/adapter/next/context.d.ts +1 -1
  9. package/dist/adapter/next/context.js +11 -3
  10. package/dist/adapter/next/handler.d.ts +2 -0
  11. package/dist/adapter/next/handler.js +23 -16
  12. package/dist/adapter/next/with-alinea.js +18 -16
  13. package/dist/backend/Handler.d.ts +14 -13
  14. package/dist/backend/Handler.js +70 -36
  15. package/dist/backend/resolver/ParsePreview.d.ts +8 -4
  16. package/dist/backend/resolver/ParsePreview.js +40 -43
  17. package/dist/backend/router/Router.d.ts +1 -0
  18. package/dist/backend/router/Router.js +21 -15
  19. package/dist/bundled.d.ts +369 -99
  20. package/dist/chunks/chunk-6UT4N4CO.js +919 -0
  21. package/dist/chunks/{chunk-LUBOGJCG.js → chunk-6V4TAD2N.js} +1 -1
  22. package/dist/chunks/{chunk-TL7ZW6BX.js → chunk-A3XXZBOQ.js} +2535 -2999
  23. package/dist/chunks/chunk-AC5ATYI3.js +1434 -0
  24. package/dist/chunks/{chunk-7ORQ7BYK.js → chunk-KMLLLZ6X.js} +202 -269
  25. package/dist/chunks/{chunk-IWN3T3FL.js → chunk-O7XWTMCB.js} +1 -1
  26. package/dist/chunks/{chunk-53DZYORR.js → chunk-PAYSVIYI.js} +76 -176
  27. package/dist/chunks/{chunk-YK3OSMDM.js → chunk-S4AU6SRS.js} +188 -158
  28. package/dist/chunks/{chunk-EQ3TDHYD.js → chunk-UVOEM4AH.js} +6 -3
  29. package/dist/chunks/{chunk-I7X64JTZ.js → chunk-VXZM6V4P.js} +4 -4
  30. package/dist/cli/Generate.js +3 -13
  31. package/dist/cli/Serve.js +7 -3
  32. package/dist/cli/bin.js +8 -5
  33. package/dist/cli/generate/CompileConfig.js +3 -2
  34. package/dist/cli/generate/CopyStaticFiles.js +2 -3
  35. package/dist/cli/generate/DevDB.d.ts +1 -1
  36. package/dist/cli/generate/DevDB.js +3 -5
  37. package/dist/cli/serve/CreateLocalServer.d.ts +1 -1
  38. package/dist/cli/serve/CreateLocalServer.js +3 -2
  39. package/dist/cli/serve/DevServer.d.ts +3 -2
  40. package/dist/cli/serve/DevServer.js +60 -3
  41. package/dist/cli/serve/ServeContext.d.ts +1 -0
  42. package/dist/cli/serve/StartServer.d.ts +1 -0
  43. package/dist/cli/serve/StartServer.js +6 -1
  44. package/dist/cli/util/EnsureLibs.js +2 -2
  45. package/dist/cli/util/EnsureNode.js +2 -2
  46. package/dist/cloud/CloudRemote.d.ts +2 -1
  47. package/dist/cloud/CloudRemote.js +3 -2
  48. package/dist/components/Breadcrumbs.js +8 -8
  49. package/dist/components/Button.js +6 -6
  50. package/dist/components/Calendar.js +11 -11
  51. package/dist/components/Checkbox.js +7 -7
  52. package/dist/components/CheckboxGroup.js +6 -6
  53. package/dist/components/ColorSwatch.js +6 -6
  54. package/dist/components/ColorSwatchPicker.js +6 -6
  55. package/dist/components/ComboBox.js +17 -17
  56. package/dist/components/DateField.js +9 -9
  57. package/dist/components/DatePicker.js +10 -10
  58. package/dist/components/DateRangePicker.js +14 -14
  59. package/dist/components/Dialog.js +6 -6
  60. package/dist/components/Disclosure.js +13 -13
  61. package/dist/components/DropZone.js +6 -6
  62. package/dist/components/Label.js +16 -16
  63. package/dist/components/Link.js +6 -6
  64. package/dist/components/List.js +34 -34
  65. package/dist/components/Menu.js +10 -10
  66. package/dist/components/Modal.js +8 -8
  67. package/dist/components/MultipleSelect.js +33 -33
  68. package/dist/components/NumberField.d.ts +2 -1
  69. package/dist/components/NumberField.js +21 -11
  70. package/dist/components/Popover.js +6 -6
  71. package/dist/components/ProgressCircle.js +10 -10
  72. package/dist/components/RadioGroup.js +6 -6
  73. package/dist/components/SearchField.js +11 -11
  74. package/dist/components/Select.js +13 -13
  75. package/dist/components/Surface.js +3 -3
  76. package/dist/components/Switch.js +6 -6
  77. package/dist/components/Table.js +13 -13
  78. package/dist/components/Tabs.js +8 -8
  79. package/dist/components/TagGroup.js +7 -7
  80. package/dist/components/TextField.js +10 -10
  81. package/dist/components/TimeField.js +6 -6
  82. package/dist/components/Toggle.js +6 -6
  83. package/dist/components/Toolbar.js +7 -7
  84. package/dist/components/Tooltip.js +10 -10
  85. package/dist/components/Tree.js +9 -9
  86. package/dist/core/BlobTransport.d.ts +5 -0
  87. package/dist/core/BlobTransport.js +79 -0
  88. package/dist/core/Client.d.ts +2 -1
  89. package/dist/core/Client.js +17 -12
  90. package/dist/core/Config.d.ts +4 -0
  91. package/dist/core/Config.js +6 -0
  92. package/dist/core/Connection.d.ts +4 -3
  93. package/dist/core/Connection.js +7 -0
  94. package/dist/core/Graph.d.ts +3 -4
  95. package/dist/core/ListRow.d.ts +2 -0
  96. package/dist/core/OrderBy.d.ts +7 -3
  97. package/dist/core/TextDoc.d.ts +11 -0
  98. package/dist/core/Trace.d.ts +8 -0
  99. package/dist/core/Trace.js +32 -0
  100. package/dist/core/Type.d.ts +7 -1
  101. package/dist/core/db/CommitRequest.d.ts +0 -3
  102. package/dist/core/db/CommitRequest.js +0 -9
  103. package/dist/core/db/EntryDB.js +1 -1
  104. package/dist/core/db/EntryIndex.d.ts +12 -2
  105. package/dist/core/db/EntryIndex.js +131 -22
  106. package/dist/core/db/EntryResolver.js +104 -29
  107. package/dist/core/db/EntryTransaction.d.ts +1 -2
  108. package/dist/core/db/EntryTransaction.js +29 -22
  109. package/dist/core/db/LocalDB.d.ts +5 -5
  110. package/dist/core/db/LocalDB.js +27 -12
  111. package/dist/core/field/RichTextField.d.ts +30 -3
  112. package/dist/core/field/RichTextField.js +124 -56
  113. package/dist/core/media/MediaAltField.d.ts +1 -0
  114. package/dist/core/media/MediaAltField.js +9 -1
  115. package/dist/core/media/MediaLocation.d.ts +5 -2
  116. package/dist/core/media/MediaLocation.js +6 -7
  117. package/dist/core/media/MediaTypes.js +11 -0
  118. package/dist/core/source/CombinedSource.d.ts +2 -2
  119. package/dist/core/source/CombinedSource.js +6 -3
  120. package/dist/core/source/FSSource.d.ts +4 -3
  121. package/dist/core/source/FSSource.js +59 -27
  122. package/dist/core/source/GithubSource.d.ts +2 -2
  123. package/dist/core/source/GithubSource.js +21 -9
  124. package/dist/core/source/IndexedDBSource.d.ts +2 -2
  125. package/dist/core/source/IndexedDBSource.js +3 -1
  126. package/dist/core/source/MemorySource.d.ts +2 -2
  127. package/dist/core/source/MemorySource.js +3 -1
  128. package/dist/core/source/OverlaySource.d.ts +12 -0
  129. package/dist/core/source/OverlaySource.js +50 -0
  130. package/dist/core/source/Source.d.ts +5 -2
  131. package/dist/core/source/Source.js +3 -3
  132. package/dist/core/util/Async.d.ts +8 -2
  133. package/dist/core/util/Async.js +67 -0
  134. package/dist/core/util/EntryFilenames.d.ts +2 -2
  135. package/dist/core/util/EntryFilenames.js +3 -3
  136. package/dist/dashboard/App.css +158 -0
  137. package/dist/dashboard/App.js +2 -2
  138. package/dist/dashboard/app/AccessDenied.js +3 -3
  139. package/dist/dashboard/app/ActivityStatus.js +15 -15
  140. package/dist/dashboard/app/AppShell.js +1 -1
  141. package/dist/dashboard/app/AuthView.js +11 -11
  142. package/dist/dashboard/app/CompactField.js +10 -10
  143. package/dist/dashboard/app/DashboardErrorBoundary.js +5 -5
  144. package/dist/dashboard/app/DashboardLayout.js +22 -19
  145. package/dist/dashboard/app/EntryHeader.js +12 -12
  146. package/dist/dashboard/app/EntryReferences.js +9 -9
  147. package/dist/dashboard/app/EntrySidebar.d.ts +1 -1
  148. package/dist/dashboard/app/EntrySidebar.js +17 -16
  149. package/dist/dashboard/app/EntrySidebarPreview.d.ts +1 -1
  150. package/dist/dashboard/app/EntrySidebarPreview.js +19 -14
  151. package/dist/dashboard/app/EntryTranslationBanner.js +7 -7
  152. package/dist/dashboard/app/Explorer.js +51 -43
  153. package/dist/dashboard/app/ExplorerCards.js +34 -34
  154. package/dist/dashboard/app/ExplorerFileCard.js +18 -18
  155. package/dist/dashboard/app/ExplorerList.js +13 -13
  156. package/dist/dashboard/app/ExplorerModal.js +3 -3
  157. package/dist/dashboard/app/ExplorerTable.js +76 -61
  158. package/dist/dashboard/app/ExternalLinkPicker.js +15 -4
  159. package/dist/dashboard/app/LinkPicker.js +8 -8
  160. package/dist/dashboard/app/LocaleMenu.js +12 -12
  161. package/dist/dashboard/app/SidebarLayout.d.ts +8 -0
  162. package/dist/dashboard/app/SidebarLayout.js +1169 -0
  163. package/dist/dashboard/app/SidebarTree.js +46 -46
  164. package/dist/dashboard/app/WorkspaceMenu.js +17 -17
  165. package/dist/dashboard/app/WorkspaceRoots.js +23 -23
  166. package/dist/dashboard/app/editor/FileEditor.d.ts +2 -1
  167. package/dist/dashboard/app/editor/FileEditor.js +41 -32
  168. package/dist/dashboard/app/editor/FilePreview.js +4 -4
  169. package/dist/dashboard/app/editor/MediaAltField.view.d.ts +1 -1
  170. package/dist/dashboard/app/editor/MediaAltField.view.js +6 -9
  171. package/dist/dashboard/app/modals/CreateEntry.js +4 -4
  172. package/dist/dashboard/app/pages/EntryPage.js +57 -31
  173. package/dist/dashboard/app/pages/UsersPage.js +33 -33
  174. package/dist/dashboard/app/ui/DashboardModal.js +23 -23
  175. package/dist/dashboard/app/ui/Rail.js +3 -3
  176. package/dist/dashboard/app/ui/Sidebar.js +2 -2
  177. package/dist/dashboard/atoms/auth.js +2 -0
  178. package/dist/dashboard/atoms/dashboard.d.ts +9 -0
  179. package/dist/dashboard/atoms/dashboard.js +17 -0
  180. package/dist/dashboard/atoms/editor.d.ts +7 -1
  181. package/dist/dashboard/atoms/editor.js +4 -1
  182. package/dist/dashboard/atoms/entry.d.ts +48 -2
  183. package/dist/dashboard/atoms/entry.js +222 -17
  184. package/dist/dashboard/atoms/explorer.js +4 -9
  185. package/dist/dashboard/atoms/preview.d.ts +3 -0
  186. package/dist/dashboard/atoms/preview.js +18 -1
  187. package/dist/dashboard/atoms/root.d.ts +4 -1
  188. package/dist/dashboard/atoms/root.js +96 -157
  189. package/dist/dashboard/hooks.d.ts +4 -2
  190. package/dist/dashboard/hooks.js +9 -5
  191. package/dist/dashboard/icons.d.ts +1 -0
  192. package/dist/dashboard/icons.js +10 -0
  193. package/dist/field/check/CheckField.view.js +2 -2
  194. package/dist/field/code/CodeField.view.js +12 -12
  195. package/dist/field/date/DateField.view.js +12 -2
  196. package/dist/field/link/FileLink.d.ts +2 -2
  197. package/dist/field/link/FileLink.js +5 -3
  198. package/dist/field/link/ImageLink.d.ts +1 -1
  199. package/dist/field/link/ImageLink.js +1 -1
  200. package/dist/field/link/Link.js +2 -2
  201. package/dist/field/link/LinkField.view.js +155 -128
  202. package/dist/field/list/ListField.view.js +22 -33
  203. package/dist/field/metadata/MetadataField.view.js +19 -19
  204. package/dist/field/number/NumberField.d.ts +2 -0
  205. package/dist/field/number/NumberField.view.js +4 -2
  206. package/dist/field/richtext/PickTextLink.d.ts +7 -0
  207. package/dist/field/richtext/PickTextLink.js +104 -2
  208. package/dist/field/richtext/ReferenceLink.d.ts +4 -1
  209. package/dist/field/richtext/ReferenceLink.js +18 -4
  210. package/dist/field/richtext/RichTextDocument.d.ts +7 -2
  211. package/dist/field/richtext/RichTextDocument.js +34 -5
  212. package/dist/field/richtext/RichTextExtensions.d.ts +3 -1
  213. package/dist/field/richtext/RichTextExtensions.js +2704 -7467
  214. package/dist/field/richtext/RichTextField.d.ts +4 -0
  215. package/dist/field/richtext/RichTextField.story.d.ts +4 -0
  216. package/dist/field/richtext/RichTextField.view.js +43 -20
  217. package/dist/field/richtext/RichTextInsertMenu.js +281 -495
  218. package/dist/field/richtext/RichTextToolbar.d.ts +5 -2
  219. package/dist/field/richtext/RichTextToolbar.js +38 -3
  220. package/dist/field/richtext/Toolbar.d.ts +19 -1
  221. package/dist/field/richtext/Toolbar.js +18 -13
  222. package/dist/field/richtext/extensions/Anchor.js +1 -1
  223. package/dist/field/richtext/extensions/Link.js +1 -1
  224. package/dist/field/richtext/extensions/Small.js +1 -1
  225. package/dist/field/select/SelectField.view.js +8 -6
  226. package/dist/index.css +227 -16
  227. package/dist/next.cjs +18 -16
  228. package/dist/next.d.ts +2 -1
  229. package/dist/next.edge.d.ts +2 -1
  230. package/dist/picker/entry/EntryPicker.js +9 -35
  231. package/dist/preview/PreviewPayload.js +35 -250
  232. package/dist/ui/RichText.d.ts +1 -0
  233. package/dist/ui/RichText.js +29 -2
  234. package/package.json +6 -3
  235. package/dist/cli/util/ExternalPlugin.d.ts +0 -2
  236. package/dist/cli/util/ExternalPlugin.js +0 -919
  237. package/dist/chunks/{chunk-JFROOUG6.js → chunk-NIVGVRDS.js} +17 -17
@@ -10,12 +10,13 @@ function createCMS() {
10
10
  );
11
11
  }
12
12
  function withAlinea(config = {}) {
13
- const adminPath = resolveAdminPath();
14
- if (!adminPath) {
13
+ const settings = resolveSettings(config);
14
+ if (!settings) {
15
15
  console.warn(
16
- "Alinea dashboard settings could not be loaded; dashboard routing is disabled. Run Next.js through the Alinea CLI and deploy @alinea/generated."
16
+ "Alinea dashboard settings were not provided; dashboard routing is disabled. Run Next.js through the Alinea CLI."
17
17
  );
18
18
  }
19
+ const adminPath = settings?.adminPath;
19
20
  let nextVersion = 15;
20
21
  try {
21
22
  const require2 = createRequire(resolve("./index.js"));
@@ -39,6 +40,10 @@ function withAlinea(config = {}) {
39
40
  };
40
41
  const redirects = adminPath ? createRedirects(config, adminPath) : config.redirects;
41
42
  const rewrites = adminPath ? createRewrites(config, adminPath) : config.rewrites;
43
+ const env = settings ? {
44
+ ...config.env,
45
+ ALINEA_ADMIN_PATH: settings.adminPath
46
+ } : config.env;
42
47
  if (nextVersion < 15)
43
48
  return {
44
49
  ...config,
@@ -51,7 +56,8 @@ function withAlinea(config = {}) {
51
56
  },
52
57
  images,
53
58
  redirects,
54
- rewrites
59
+ rewrites,
60
+ env
55
61
  };
56
62
  return {
57
63
  ...config,
@@ -61,7 +67,8 @@ function withAlinea(config = {}) {
61
67
  ],
62
68
  images,
63
69
  redirects,
64
- rewrites
70
+ rewrites,
71
+ env
65
72
  };
66
73
  }
67
74
  function createRedirects(config, adminPath) {
@@ -124,17 +131,12 @@ function createRewrites(config, adminPath) {
124
131
  };
125
132
  };
126
133
  }
127
- function resolveAdminPath() {
128
- try {
129
- const require2 = createRequire(resolve("./index.js"));
130
- const location = require2.resolve("@alinea/generated/settings.json");
131
- const settings = JSON.parse(
132
- readFileSync(location, "utf-8")
133
- );
134
- if (typeof settings.adminPath === "string" && settings.adminPath)
135
- return normalizeBasePath(settings.adminPath);
136
- } catch {
137
- }
134
+ function resolveSettings(config) {
135
+ const adminPath = config.env?.ALINEA_ADMIN_PATH ?? process.env.ALINEA_ADMIN_PATH;
136
+ if (!adminPath) return;
137
+ return {
138
+ adminPath: normalizeBasePath(adminPath)
139
+ };
138
140
  }
139
141
  function normalizeBasePath(value) {
140
142
  return value.startsWith("/") ? value : `/${value}`;
@@ -1,24 +1,25 @@
1
- import type { Entry } from 'alinea/core';
2
1
  import type { CMS } from 'alinea/core/CMS';
3
- import type { RemoteConnection, RequestContext } from 'alinea/core/Connection';
4
- import type { LocalDB } from 'alinea/core/db/LocalDB';
2
+ import type { AuthedContext, RemoteConnection, RequestContext } from 'alinea/core/Connection';
3
+ import { LocalDB } from 'alinea/core/db/LocalDB';
4
+ import type { Mutation } from 'alinea/core/db/Mutation';
5
5
  export interface Handler {
6
6
  (request: Request, context: RequestContext): Promise<Response>;
7
7
  }
8
- export type HookResponse<T = void> = void | T | Promise<T> | Promise<void>;
8
+ export type HookResponse<T = void> = void | T | Promise<void | T>;
9
+ export interface BeforeCommitContext {
10
+ mutations: ReadonlyArray<Mutation>;
11
+ }
12
+ export interface AfterCommitContext extends BeforeCommitContext {
13
+ sha: string;
14
+ }
9
15
  export interface HandlerHooks {
10
- beforeCreate?(entry: Entry): HookResponse<Entry>;
11
- afterCreate?(entry: Entry): HookResponse;
12
- beforeUpdate?(entry: Entry): HookResponse<Entry>;
13
- afterUpdate?(entry: Entry): HookResponse;
14
- beforeArchive?(entryId: string): HookResponse;
15
- afterArchive?(entryId: string): HookResponse;
16
- beforeRemove?(entryId: string): HookResponse;
17
- afterRemove?(entryId: string): HookResponse;
16
+ beforeCommit?(context: BeforeCommitContext): HookResponse<ReadonlyArray<Mutation>>;
17
+ afterCommit?(context: AfterCommitContext): HookResponse;
18
18
  }
19
19
  export interface HandlerOptions extends HandlerHooks {
20
20
  cms: CMS;
21
21
  db: LocalDB | Promise<LocalDB>;
22
22
  remote?: (context: RequestContext) => RemoteConnection;
23
+ forwardMutations?(request: Request, context: AuthedContext): Promise<Response | undefined>;
23
24
  }
24
- export declare function createHandler({ cms, remote, db, ...hooks }: HandlerOptions): Handler;
25
+ export declare function createHandler({ cms, remote, db, forwardMutations, ...hooks }: HandlerOptions): Handler;
@@ -13,6 +13,11 @@ import "../chunks/chunk-CQ52GEHY.js";
13
13
  // src/backend/Handler.ts
14
14
  import { JWTPreviews } from "alinea/backend/util/JWTPreviews";
15
15
  import { CloudRemote } from "alinea/cloud/CloudRemote";
16
+ import {
17
+ BLOB_SEQUENCE_CONTENT_TYPE,
18
+ encodeBlobSequence
19
+ } from "alinea/core/BlobTransport";
20
+ import { developmentKeyHeader } from "alinea/core/Connection";
16
21
  import { ErrorCode, HttpError } from "alinea/core/HttpError";
17
22
  import { assertUploadSize } from "alinea/core/media/UploadLimits";
18
23
  import { Permission, Policy } from "alinea/core/Role";
@@ -22,7 +27,8 @@ import { base64 } from "alinea/core/util/Encoding";
22
27
  import { isRecord } from "alinea/core/util/Objects";
23
28
  import { InvalidCredentialsError, MissingCredentialsError } from "./Auth.js";
24
29
  import { HandleAction } from "./HandleAction.js";
25
- import { createPreviewParser } from "./resolver/ParsePreview.js";
30
+ import { applyPreview, decodePreviewRequest } from "./resolver/ParsePreview.js";
31
+ import { compressResponse } from "./router/Router.js";
26
32
  import { createThrottledSync } from "./util/Syncable.js";
27
33
  var PrepareBody = object({
28
34
  filename: string,
@@ -32,13 +38,10 @@ function createHandler({
32
38
  cms,
33
39
  remote = (context) => new CloudRemote(context, cms.config),
34
40
  db,
41
+ forwardMutations,
35
42
  ...hooks
36
43
  }) {
37
44
  const throttle = createThrottledSync();
38
- const previewParser = PLazy.from(async () => {
39
- const local = await db;
40
- return createPreviewParser(local);
41
- });
42
45
  return async function handle(request, context) {
43
46
  const dev = process.env.ALINEA_DEV_SERVER;
44
47
  const local = await db;
@@ -88,11 +91,10 @@ function createHandler({
88
91
  };
89
92
  } catch (cause) {
90
93
  if (cause instanceof MissingCredentialsError) {
91
- const authorization = request.headers.get("authorization");
92
- const bearer = authorization?.slice("Bearer ".length);
94
+ const credential = context.isDev ? request.headers.get(developmentKeyHeader) : request.headers.get("authorization")?.slice("Bearer ".length);
93
95
  if (!context.apiKey)
94
96
  throw new MissingCredentialsError("Missing API key", { cause });
95
- if (bearer !== context.apiKey)
97
+ if (credential !== context.apiKey)
96
98
  throw new InvalidCredentialsError("Expected matching api key", {
97
99
  cause
98
100
  });
@@ -159,43 +161,77 @@ function createHandler({
159
161
  const scope = getScope(cms.config);
160
162
  const query = scope.parse(raw);
161
163
  if (!query.preview) {
162
- await periodicSync(cnx, query.syncInterval);
164
+ await periodicSync(
165
+ cnx,
166
+ query.disableSync ? Number.POSITIVE_INFINITY : query.syncInterval ?? cms.config.syncInterval
167
+ );
163
168
  } else {
164
- const { parse } = await previewParser;
165
- const preview = await parse(query.preview, () => local.syncWith(cnx));
166
- query.preview = preview;
169
+ const preview = await decodePreviewRequest(query.preview);
170
+ if ("contentHash" in preview && local.sha !== preview.contentHash)
171
+ await local.syncWith(cnx);
172
+ query.preview = await applyPreview(local, preview);
167
173
  }
168
174
  return Response.json(await local.resolve(query) ?? null);
169
175
  }
170
176
  if (action === HandleAction.Mutate && request.method === "POST") {
177
+ if (forwardMutations && userCtx) {
178
+ const forwarded = await forwardMutations(request.clone(), userCtx);
179
+ if (forwarded) return forwarded;
180
+ }
171
181
  const user = expectUser();
172
182
  expectJson();
173
183
  const policy = await user.policy;
174
- const mutations = await body;
184
+ let mutations = await body;
185
+ await local.syncWith(cnx);
186
+ const adjusted = await hooks.beforeCommit?.({ mutations });
187
+ if (adjusted) mutations = adjusted;
175
188
  const attempt = async (retry = 0) => {
176
- await local.syncWith(cnx);
189
+ if (retry > 0) await local.syncWith(cnx);
177
190
  const request2 = {
178
191
  ...await local.request(mutations, policy),
179
192
  user: user.claims
180
193
  };
181
194
  try {
182
- let { sha } = await cnx.write(request2);
183
- if (sha === request2.intoSha) {
195
+ let { sha: sha2 } = await cnx.write(request2);
196
+ if (sha2 === request2.intoSha) {
184
197
  await local.write(request2);
185
198
  } else {
186
- sha = await local.syncWith(cnx);
199
+ sha2 = await local.syncWith(cnx);
187
200
  }
188
- return sha;
201
+ return sha2;
189
202
  } catch (error) {
190
- if (error instanceof ShaMismatchError && retry < 3)
191
- return attempt(retry + 1);
203
+ const isConflict = error instanceof ShaMismatchError || error instanceof HttpError && error.code === 409 || error instanceof Response && error.status === 409;
204
+ if (isConflict && retry < 3) return attempt(retry + 1);
192
205
  throw error;
193
206
  }
194
207
  };
195
- return Response.json({ sha: await attempt() });
208
+ const sha = await attempt();
209
+ try {
210
+ await hooks.afterCommit?.({
211
+ mutations,
212
+ sha
213
+ });
214
+ } catch (error) {
215
+ console.error("Alinea afterCommit hook failed", error);
216
+ }
217
+ return Response.json({ sha });
196
218
  }
197
219
  if (action === HandleAction.Commit && request.method === "POST") {
198
- throw new Error("Mutations expected");
220
+ if (!context.isDev)
221
+ throw new HttpError(400, "Commits are only accepted in development");
222
+ const developmentKey = request.headers.get(developmentKeyHeader);
223
+ if (!context.apiKey || developmentKey !== context.apiKey)
224
+ throw new HttpError(401, "Invalid development commit credentials");
225
+ const user = expectUser();
226
+ expectJson();
227
+ const commit = {
228
+ ...await body,
229
+ user: user.claims
230
+ };
231
+ let { sha } = await cnx.write(commit);
232
+ if (sha === commit.intoSha) await local.write(commit);
233
+ else sha = await local.syncWith(cnx);
234
+ return Response.json({ sha });
199
235
  }
200
236
  if (action === HandleAction.History && request.method === "GET") {
201
237
  expectUser();
@@ -210,7 +246,7 @@ function createHandler({
210
246
  const sha = string(url.searchParams.get("sha"));
211
247
  await local.syncWith(cnx);
212
248
  const tree = await local.getTreeIfDifferent(sha);
213
- return Response.json(tree ?? null);
249
+ return compressResponse(request, Response.json(tree ?? null));
214
250
  }
215
251
  if (action === HandleAction.Blob && request.method === "POST") {
216
252
  const { shas } = object({ shas: array(string) })(await body);
@@ -222,20 +258,18 @@ function createHandler({
222
258
  if (tree.hasSha(sha)) fromLocal.push(sha);
223
259
  else fromRemote.push(sha);
224
260
  }
225
- const formData = new FormData();
226
- if (fromLocal.length > 0) {
227
- const blobs = local.source.getBlobs(fromLocal);
228
- for await (const [sha, blob] of blobs) {
229
- formData.append(sha, new Blob([blob]));
230
- }
261
+ async function* blobs() {
262
+ const options = { signal: request.signal };
263
+ if (fromLocal.length > 0)
264
+ yield* local.source.getBlobs(fromLocal, options);
265
+ if (fromRemote.length > 0) yield* cnx.getBlobs(fromRemote, options);
231
266
  }
232
- if (fromRemote.length > 0) {
233
- const blobs = cnx.getBlobs(fromRemote);
234
- for await (const [sha, blob] of blobs) {
235
- formData.append(sha, new Blob([blob]));
236
- }
237
- }
238
- return new Response(formData);
267
+ return compressResponse(
268
+ request,
269
+ new Response(encodeBlobSequence(blobs()), {
270
+ headers: { "content-type": BLOB_SEQUENCE_CONTENT_TYPE }
271
+ })
272
+ );
239
273
  }
240
274
  if (action === HandleAction.Upload) {
241
275
  const user = expectUser();
@@ -1,5 +1,9 @@
1
1
  import type { LocalDB } from 'alinea/core/db/LocalDB';
2
- import type { PreviewRequest } from 'alinea/core/Preview';
3
- export declare function createPreviewParser(local: LocalDB): {
4
- parse(preview: PreviewRequest, sync: () => Promise<unknown>): Promise<PreviewRequest | undefined>;
5
- };
2
+ import { Entry } from 'alinea/core/Entry';
3
+ import type { PreviewRequest, PreviewUpdate } from 'alinea/core/Preview';
4
+ export interface DecodedEntryPreview {
5
+ entry: Entry;
6
+ }
7
+ export type DecodedPreviewRequest = PreviewUpdate | DecodedEntryPreview;
8
+ export declare function decodePreviewRequest(preview: PreviewRequest): Promise<DecodedPreviewRequest>;
9
+ export declare function applyPreview(local: LocalDB, preview: DecodedPreviewRequest): Promise<PreviewRequest | undefined>;
@@ -5,53 +5,50 @@ import { JsonLoader } from "alinea/backend/loader/JsonLoader";
5
5
  import { Entry } from "alinea/core/Entry";
6
6
  import { createRecord, parseRecord } from "alinea/core/EntryRecord";
7
7
  import { applyFilePatch } from "alinea/core/source/FilePatch";
8
+ import { trace } from "alinea/core/Trace";
8
9
  import { createEntryRow } from "alinea/core/util/EntryRows";
9
10
  import { decodePreviewPayload } from "alinea/preview/PreviewPayload";
10
11
  var decoder = new TextDecoder();
11
- function createPreviewParser(local) {
12
- return {
13
- async parse(preview, sync) {
14
- if (!(preview && "payload" in preview)) return preview;
15
- const payload = await decodePreviewPayload(preview.payload);
16
- if (local.sha !== payload.contentHash) {
17
- await sync();
18
- if (local.sha !== payload.contentHash) return;
19
- }
20
- const entry = await local.first({
21
- select: Entry,
22
- id: payload.entryId,
23
- locale: payload.locale,
24
- status: "preferDraft"
25
- });
26
- if (!entry) return;
27
- const baseText = decoder.decode(
28
- JsonLoader.format(
29
- local.config.schema,
30
- createRecord(entry, entry.status)
31
- )
32
- );
33
- let updatedText;
34
- try {
35
- updatedText = await applyFilePatch(baseText, payload.patch);
36
- } catch {
37
- return;
38
- }
39
- const { data } = parseRecord(JSON.parse(updatedText));
40
- const { rowHash: _rowHash, fileHash: _fileHash, ...withoutHashes } = entry;
41
- const patched = await createEntryRow(
42
- local.config,
43
- {
44
- ...withoutHashes,
45
- title: data.title,
46
- data,
47
- path: entry.path
48
- },
49
- entry.status
50
- );
51
- return { entry: patched };
12
+ async function decodePreviewRequest(preview) {
13
+ if ("entry" in preview) return preview;
14
+ return decodePreviewPayload(preview.payload);
15
+ }
16
+ async function applyPreview(local, preview) {
17
+ if ("entry" in preview) return preview;
18
+ const span = trace(local.config, "alinea.preview.apply");
19
+ return span(async () => {
20
+ const entry = await local.first({
21
+ select: Entry,
22
+ id: preview.entryId,
23
+ locale: preview.locale,
24
+ status: "preferDraft"
25
+ });
26
+ if (!entry) return;
27
+ const baseText = decoder.decode(
28
+ JsonLoader.format(local.config.schema, createRecord(entry, entry.status))
29
+ );
30
+ let updatedText;
31
+ try {
32
+ updatedText = await applyFilePatch(baseText, preview.patch);
33
+ } catch {
34
+ return;
52
35
  }
53
- };
36
+ const { data } = parseRecord(JSON.parse(updatedText));
37
+ const { rowHash: _rowHash, fileHash: _fileHash, ...withoutHashes } = entry;
38
+ const patched = await createEntryRow(
39
+ local.config,
40
+ {
41
+ ...withoutHashes,
42
+ title: data.title,
43
+ data,
44
+ path: entry.path
45
+ },
46
+ entry.status
47
+ );
48
+ return { entry: patched };
49
+ });
54
50
  }
55
51
  export {
56
- createPreviewParser
52
+ applyPreview,
53
+ decodePreviewRequest
57
54
  };
@@ -144,4 +144,5 @@ export declare namespace router {
144
144
  function cookie(...cookies: Array<Cookie>): string;
145
145
  function compress(...routes: Array<Next<Request, Response | undefined>>): Route<Request, Response | undefined>;
146
146
  }
147
+ export declare function compressResponse(request: Request, response: Response): Response;
147
148
  export {};
@@ -186,27 +186,33 @@ function callHandler(handler, input) {
186
186
  async (request) => {
187
187
  const response = await route.handle(request);
188
188
  if (response === void 0) return void 0;
189
- const body = response.body;
190
- if (!body) return response;
191
- const isCompressed = response.headers.get("content-encoding");
192
- if (isCompressed) return response;
193
- const accept = request.headers.get("accept-encoding");
194
- const method = accept?.includes("gzip") ? "gzip" : accept?.includes("deflate") ? "deflate" : void 0;
195
- if (method === void 0) return response;
196
- const stream = body.pipeThrough(new CompressionStream(method));
197
- const headers = new Headers(response.headers);
198
- headers.set("content-encoding", method);
199
- headers.delete("content-length");
200
- return new Response(stream, {
201
- headers,
202
- status: response.status
203
- });
189
+ return compressResponse(request, response);
204
190
  }
205
191
  );
206
192
  }
207
193
  router2.compress = compress;
208
194
  })(router || (router = {}));
195
+ function compressResponse(request, response) {
196
+ const body = response.body;
197
+ if (!body) return response;
198
+ const isCompressed = response.headers.get("content-encoding");
199
+ if (isCompressed) return response;
200
+ const accept = request.headers.get("accept-encoding");
201
+ const method = accept?.includes("gzip") ? "gzip" : accept?.includes("deflate") ? "deflate" : void 0;
202
+ if (method === void 0) return response;
203
+ const stream = body.pipeThrough(new CompressionStream(method));
204
+ const headers = new Headers(response.headers);
205
+ headers.set("content-encoding", method);
206
+ headers.set("vary", "accept-encoding");
207
+ headers.delete("content-length");
208
+ return new Response(stream, {
209
+ headers,
210
+ status: response.status,
211
+ statusText: response.statusText
212
+ });
213
+ }
209
214
  export {
210
215
  Route,
216
+ compressResponse,
211
217
  router
212
218
  };