@mp-lb/mdkit 0.3.2 → 0.3.4

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 (160) hide show
  1. package/README.md +8 -2
  2. package/dist/collaboration/useMdKitCollaboration.d.ts +5 -0
  3. package/dist/collaboration/useMdKitCollaboration.d.ts.map +1 -0
  4. package/dist/collaboration/useMdKitCollaboration.js +4 -0
  5. package/dist/core/checkpointPolicy.d.ts +10 -0
  6. package/dist/core/checkpointPolicy.d.ts.map +1 -0
  7. package/dist/core/checkpointPolicy.js +9 -0
  8. package/dist/core/documentEngine.d.ts +1 -0
  9. package/dist/core/documentEngine.d.ts.map +1 -0
  10. package/dist/core/index.d.ts +1 -0
  11. package/dist/core/index.d.ts.map +1 -0
  12. package/dist/document/MdKitConflictPanel.d.ts +5 -0
  13. package/dist/document/MdKitConflictPanel.d.ts.map +1 -0
  14. package/dist/document/MdKitConflictPanel.js +4 -0
  15. package/dist/document/MdKitDocumentToolbar.d.ts +6 -0
  16. package/dist/document/MdKitDocumentToolbar.d.ts.map +1 -0
  17. package/dist/document/MdKitDocumentToolbar.js +5 -0
  18. package/dist/document/documentTypes.d.ts +6 -0
  19. package/dist/document/documentTypes.d.ts.map +1 -0
  20. package/dist/document/useMdKitDocument.d.ts +5 -0
  21. package/dist/document/useMdKitDocument.d.ts.map +1 -0
  22. package/dist/document/useMdKitDocument.js +4 -0
  23. package/dist/fastify.d.ts +1 -0
  24. package/dist/fastify.d.ts.map +1 -0
  25. package/dist/index.d.ts +4 -1
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/markdown/MarkdownBubbleMenu.d.ts +1 -0
  28. package/dist/markdown/MarkdownBubbleMenu.d.ts.map +1 -0
  29. package/dist/markdown/MarkdownPasteExtension.d.ts +1 -0
  30. package/dist/markdown/MarkdownPasteExtension.d.ts.map +1 -0
  31. package/dist/markdown/MarkdownSearchExtension.d.ts +1 -0
  32. package/dist/markdown/MarkdownSearchExtension.d.ts.map +1 -0
  33. package/dist/markdown/MarkdownSearchPanel.d.ts +1 -0
  34. package/dist/markdown/MarkdownSearchPanel.d.ts.map +1 -0
  35. package/dist/markdown/MdKitEditor.d.ts +11 -0
  36. package/dist/markdown/MdKitEditor.d.ts.map +1 -0
  37. package/dist/markdown/MdKitEditor.js +10 -2
  38. package/dist/markdown/MdKitView.d.ts +9 -1
  39. package/dist/markdown/MdKitView.d.ts.map +1 -0
  40. package/dist/markdown/MdKitView.js +7 -2
  41. package/dist/markdown/TiptapMarkdownSurface.d.ts +1 -0
  42. package/dist/markdown/TiptapMarkdownSurface.d.ts.map +1 -0
  43. package/dist/markdown/TiptapMarkdownSurface.js +3 -22
  44. package/dist/markdown/createMdKitTiptapExtensions.d.ts +1 -0
  45. package/dist/markdown/createMdKitTiptapExtensions.d.ts.map +1 -0
  46. package/dist/markdown/editorDebug.d.ts +1 -0
  47. package/dist/markdown/editorDebug.d.ts.map +1 -0
  48. package/dist/markdown/markdownFenceRanges.d.ts +1 -0
  49. package/dist/markdown/markdownFenceRanges.d.ts.map +1 -0
  50. package/dist/markdown/normalizeMarkdownSerialization.d.ts +1 -0
  51. package/dist/markdown/normalizeMarkdownSerialization.d.ts.map +1 -0
  52. package/dist/markdown/prepareMarkdownForEditorHydration.d.ts +1 -0
  53. package/dist/markdown/prepareMarkdownForEditorHydration.d.ts.map +1 -0
  54. package/dist/markdown/preserveMarkdownWhitespace.d.ts +1 -0
  55. package/dist/markdown/preserveMarkdownWhitespace.d.ts.map +1 -0
  56. package/dist/markdown/yamlFrontMatter.d.ts +1 -0
  57. package/dist/markdown/yamlFrontMatter.d.ts.map +1 -0
  58. package/dist/server.d.ts +1 -0
  59. package/dist/server.d.ts.map +1 -0
  60. package/dist/theme/MdKitThemeEditor.d.ts +5 -0
  61. package/dist/theme/MdKitThemeEditor.d.ts.map +1 -0
  62. package/dist/theme/MdKitThemeEditor.js +4 -0
  63. package/dist/theme/editorTheme.d.ts +1 -0
  64. package/dist/theme/editorTheme.d.ts.map +1 -0
  65. package/dist/theme/editorTheme.js +8 -8
  66. package/dist/transport/backend.d.ts +13 -0
  67. package/dist/transport/backend.d.ts.map +1 -0
  68. package/dist/transport/backend.js +6 -0
  69. package/dist/transport/fastify.d.ts +5 -0
  70. package/dist/transport/fastify.d.ts.map +1 -0
  71. package/dist/transport/fastify.js +4 -0
  72. package/dist/transport/http.d.ts +1 -0
  73. package/dist/transport/http.d.ts.map +1 -0
  74. package/dist/transport/index.d.ts +1 -0
  75. package/dist/transport/index.d.ts.map +1 -0
  76. package/dist/transport/rest.d.ts +6 -0
  77. package/dist/transport/rest.d.ts.map +1 -0
  78. package/dist/transport/rest.js +5 -0
  79. package/dist/transport/store.d.ts +1 -0
  80. package/dist/transport/store.d.ts.map +1 -0
  81. package/dist/transport/trpcClient.d.ts +8 -0
  82. package/dist/transport/trpcClient.d.ts.map +1 -0
  83. package/dist/transport/trpcClient.js +7 -0
  84. package/dist/transport/trpcServer.d.ts +6 -0
  85. package/dist/transport/trpcServer.d.ts.map +1 -0
  86. package/dist/transport/trpcServer.js +5 -0
  87. package/dist/trpc/client.d.ts +1 -0
  88. package/dist/trpc/client.d.ts.map +1 -0
  89. package/dist/trpc/server.d.ts +1 -0
  90. package/dist/trpc/server.d.ts.map +1 -0
  91. package/dist/trpc.d.ts +1 -0
  92. package/dist/trpc.d.ts.map +1 -0
  93. package/dist/ui/joinClassNames.d.ts +1 -0
  94. package/dist/ui/joinClassNames.d.ts.map +1 -0
  95. package/dist/versioning/VersionHistoryPanel.d.ts +5 -0
  96. package/dist/versioning/VersionHistoryPanel.d.ts.map +1 -0
  97. package/dist/versioning/VersionHistoryPanel.js +4 -0
  98. package/dist/versioning/useMdKitDocumentVersions.d.ts +5 -0
  99. package/dist/versioning/useMdKitDocumentVersions.d.ts.map +1 -0
  100. package/dist/versioning/useMdKitDocumentVersions.js +4 -0
  101. package/dist/yjs/MdKitMarkdownYjs.d.ts +1 -0
  102. package/dist/yjs/MdKitMarkdownYjs.d.ts.map +1 -0
  103. package/dist/yjs/index.d.ts +1 -0
  104. package/dist/yjs/index.d.ts.map +1 -0
  105. package/package.json +10 -12
  106. package/src/collaboration/useMdKitCollaboration.ts +528 -0
  107. package/src/core/checkpointPolicy.ts +107 -0
  108. package/src/core/documentEngine.ts +175 -0
  109. package/src/core/index.ts +33 -0
  110. package/src/document/MdKitConflictPanel.tsx +129 -0
  111. package/src/document/MdKitDocumentToolbar.tsx +141 -0
  112. package/src/document/documentTypes.ts +89 -0
  113. package/src/document/useMdKitDocument.ts +543 -0
  114. package/src/fastify.ts +6 -0
  115. package/src/index.ts +89 -0
  116. package/src/markdown/MarkdownBubbleMenu.tsx +271 -0
  117. package/src/markdown/MarkdownPasteExtension.ts +81 -0
  118. package/src/markdown/MarkdownSearchExtension.ts +77 -0
  119. package/src/markdown/MarkdownSearchPanel.tsx +98 -0
  120. package/src/markdown/MdKitEditor.tsx +75 -0
  121. package/src/markdown/MdKitView.tsx +80 -0
  122. package/src/markdown/TiptapMarkdownSurface.tsx +923 -0
  123. package/src/markdown/createMdKitTiptapExtensions.ts +42 -0
  124. package/src/markdown/editorDebug.ts +5 -0
  125. package/src/markdown/markdownFenceRanges.ts +68 -0
  126. package/src/markdown/normalizeMarkdownSerialization.ts +55 -0
  127. package/src/markdown/prepareMarkdownForEditorHydration.ts +23 -0
  128. package/src/markdown/preserveMarkdownWhitespace.ts +143 -0
  129. package/src/markdown/yamlFrontMatter.ts +135 -0
  130. package/src/server.ts +6 -0
  131. package/src/styles.css +132 -53
  132. package/src/theme/MdKitThemeEditor.tsx +134 -0
  133. package/src/theme/editorTheme.ts +72 -0
  134. package/src/transport/backend.ts +220 -0
  135. package/src/transport/fastify.ts +57 -0
  136. package/src/transport/http.ts +126 -0
  137. package/src/transport/index.ts +12 -0
  138. package/src/transport/rest.ts +80 -0
  139. package/src/transport/store.ts +45 -0
  140. package/src/transport/trpcClient.ts +90 -0
  141. package/src/transport/trpcServer.ts +66 -0
  142. package/src/trpc/client.ts +11 -0
  143. package/src/trpc/server.ts +12 -0
  144. package/src/trpc.ts +11 -0
  145. package/src/ui/joinClassNames.ts +3 -0
  146. package/src/versioning/VersionHistoryPanel.tsx +146 -0
  147. package/src/versioning/useMdKitDocumentVersions.ts +146 -0
  148. package/src/yjs/MdKitMarkdownYjs.ts +111 -0
  149. package/src/yjs/index.ts +8 -0
  150. package/docs/.vitepress/config.ts +0 -47
  151. package/docs/api.md +0 -512
  152. package/docs/architecture.md +0 -96
  153. package/docs/collaboration-persistence.md +0 -147
  154. package/docs/index.md +0 -341
  155. package/docs/permissions.md +0 -139
  156. package/docs/plain-text.md +0 -131
  157. package/docs/rest.md +0 -98
  158. package/docs/shadcn.md +0 -125
  159. package/docs/styling.md +0 -373
  160. package/docs/use-cases.md +0 -148
@@ -0,0 +1,57 @@
1
+ import type { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
2
+ import { createMdKitHttpHandlers } from "./http";
3
+ import type { MdKitTransportStore } from "./store";
4
+
5
+ export type RegisterMdKitFastifyOptions = {
6
+ prefix?: string;
7
+ store: MdKitTransportStore;
8
+ };
9
+
10
+ const send = async (
11
+ reply: FastifyReply,
12
+ response: Promise<{ body: unknown; status: number }>,
13
+ ) => {
14
+ const { body, status } = await response;
15
+ return reply.status(status).send(body);
16
+ };
17
+
18
+ const toRequest = (request: FastifyRequest) => ({
19
+ body: request.body,
20
+ params: request.params as Record<string, unknown>,
21
+ query: request.query as Record<string, unknown>,
22
+ });
23
+
24
+ /**
25
+ * Registers the mdkit REST endpoints on a Fastify app under `prefix`, backed by
26
+ * a transport store (typically from `createMdKitBackend`).
27
+ */
28
+ export const registerMdKitFastify = async (
29
+ app: FastifyInstance,
30
+ { prefix = "", store }: RegisterMdKitFastifyOptions,
31
+ ) => {
32
+ const handlers = createMdKitHttpHandlers(store);
33
+
34
+ app.get(`${prefix}/documents`, (request, reply) =>
35
+ send(reply, handlers.readDocument(toRequest(request))),
36
+ );
37
+
38
+ app.put(`${prefix}/documents`, (request, reply) =>
39
+ send(reply, handlers.writeDocument(toRequest(request))),
40
+ );
41
+
42
+ app.post(`${prefix}/documents/resync`, (request, reply) =>
43
+ send(reply, handlers.resyncDocument(toRequest(request))),
44
+ );
45
+
46
+ app.get(`${prefix}/versions`, (request, reply) =>
47
+ send(reply, handlers.listDocumentVersions(toRequest(request))),
48
+ );
49
+
50
+ app.get(`${prefix}/versions/:versionId`, (request, reply) =>
51
+ send(reply, handlers.readDocumentVersion(toRequest(request))),
52
+ );
53
+
54
+ app.post(`${prefix}/versions/:versionId/restore`, (request, reply) =>
55
+ send(reply, handlers.restoreDocumentVersion(toRequest(request))),
56
+ );
57
+ };
@@ -0,0 +1,126 @@
1
+ import type {
2
+ MdKitDocumentVersionToken,
3
+ MdKitDocumentWriteInput,
4
+ } from "../document/documentTypes";
5
+ import type {
6
+ MdKitRestoreDocumentVersionInput,
7
+ MdKitTransportStore,
8
+ } from "./store";
9
+
10
+ export type MdKitHttpRequest = {
11
+ body?: unknown;
12
+ params?: Record<string, unknown>;
13
+ query?: Record<string, unknown>;
14
+ };
15
+
16
+ export type MdKitHttpResponse<T = unknown> = {
17
+ body: T;
18
+ status: number;
19
+ };
20
+
21
+ export type MdKitHttpHandlers = ReturnType<typeof createMdKitHttpHandlers>;
22
+
23
+ const readString = (
24
+ source: Record<string, unknown> | undefined,
25
+ key: string,
26
+ ): string => {
27
+ const value = source?.[key];
28
+
29
+ if (typeof value !== "string" || value.length === 0) {
30
+ throw new Error(`Missing mdkit ${key}`);
31
+ }
32
+
33
+ return value;
34
+ };
35
+
36
+ const readVersionToken = (value: unknown): MdKitDocumentVersionToken => {
37
+ if (
38
+ value === null ||
39
+ typeof value === "string" ||
40
+ typeof value === "number"
41
+ ) {
42
+ return value;
43
+ }
44
+
45
+ throw new Error("Invalid mdkit baseVersion");
46
+ };
47
+
48
+ const readWriteInput = (request: MdKitHttpRequest): MdKitDocumentWriteInput => {
49
+ const body = request.body as Record<string, unknown> | null | undefined;
50
+
51
+ if (!body || typeof body !== "object") {
52
+ throw new Error("Missing mdkit write body");
53
+ }
54
+
55
+ const content = body.content;
56
+
57
+ if (typeof content !== "string") {
58
+ throw new Error("Invalid mdkit content");
59
+ }
60
+
61
+ return {
62
+ baseVersion: readVersionToken(body.baseVersion),
63
+ content,
64
+ documentId: readString(request.query, "documentId"),
65
+ force: body.force === true,
66
+ };
67
+ };
68
+
69
+ const notFound = (message: string): MdKitHttpResponse<{ error: string }> => ({
70
+ body: { error: message },
71
+ status: 404,
72
+ });
73
+
74
+ export const createMdKitHttpHandlers = (store: MdKitTransportStore) => ({
75
+ readDocument: async (request: MdKitHttpRequest) => ({
76
+ body: await store.readDocument(readString(request.query, "documentId")),
77
+ status: 200,
78
+ }),
79
+ writeDocument: async (request: MdKitHttpRequest) => {
80
+ const body = await store.writeDocument(readWriteInput(request));
81
+
82
+ return {
83
+ body,
84
+ status: "conflict" in body ? 409 : 200,
85
+ };
86
+ },
87
+ resyncDocument: async (request: MdKitHttpRequest) => ({
88
+ body: await (store.resyncDocument ?? store.readDocument)(
89
+ readString(request.query, "documentId"),
90
+ ),
91
+ status: 200,
92
+ }),
93
+ listDocumentVersions: async (request: MdKitHttpRequest) => ({
94
+ body: {
95
+ versions: await (store.listDocumentVersions?.(
96
+ readString(request.query, "documentId"),
97
+ ) ?? []),
98
+ },
99
+ status: 200,
100
+ }),
101
+ readDocumentVersion: async (request: MdKitHttpRequest) => {
102
+ const version = await store.readDocumentVersion?.({
103
+ documentId: readString(request.query, "documentId"),
104
+ versionId: readString(request.params, "versionId"),
105
+ });
106
+
107
+ return version
108
+ ? ({ body: version, status: 200 } as const)
109
+ : notFound("Version not found");
110
+ },
111
+ restoreDocumentVersion: async (request: MdKitHttpRequest) => {
112
+ if (!store.restoreDocumentVersion) {
113
+ return notFound("Version restore is not supported");
114
+ }
115
+
116
+ const input: MdKitRestoreDocumentVersionInput = {
117
+ documentId: readString(request.query, "documentId"),
118
+ versionId: readString(request.params, "versionId"),
119
+ };
120
+
121
+ return {
122
+ body: await store.restoreDocumentVersion(input),
123
+ status: 200,
124
+ };
125
+ },
126
+ });
@@ -0,0 +1,12 @@
1
+ export { createMdKitHttpHandlers } from "./http";
2
+ export { createMdKitRestAdapter } from "./rest";
3
+ export type {
4
+ MdKitHttpHandlers,
5
+ MdKitHttpRequest,
6
+ MdKitHttpResponse,
7
+ } from "./http";
8
+ export type { CreateMdKitRestAdapterOptions } from "./rest";
9
+ export type {
10
+ MdKitRestoreDocumentVersionInput,
11
+ MdKitTransportStore,
12
+ } from "./store";
@@ -0,0 +1,80 @@
1
+ import type {
2
+ MdKitDocumentAdapter,
3
+ MdKitDocumentVersionSummary,
4
+ } from "../document/documentTypes";
5
+
6
+ export type CreateMdKitRestAdapterOptions = {
7
+ baseUrl: string;
8
+ fetch?: typeof fetch;
9
+ };
10
+
11
+ const trimTrailingSlash = (value: string) => value.replace(/\/+$/, "");
12
+
13
+ const documentQuery = (documentId: string) =>
14
+ `documentId=${encodeURIComponent(documentId)}`;
15
+
16
+ const readJson = async <T>(response: Response): Promise<T> => {
17
+ const body = (await response.json()) as T;
18
+
19
+ if (!response.ok && response.status !== 409) {
20
+ throw new Error(`MdKit REST request failed: ${response.status}`);
21
+ }
22
+
23
+ return body;
24
+ };
25
+
26
+ /**
27
+ * Builds an {@link MdKitDocumentAdapter} that talks to the mdkit REST endpoint
28
+ * shape. Restore is not part of the adapter contract, so REST restore needs a
29
+ * separate call from application code.
30
+ */
31
+ export const createMdKitRestAdapter = ({
32
+ baseUrl,
33
+ fetch: fetchImpl = fetch,
34
+ }: CreateMdKitRestAdapterOptions): MdKitDocumentAdapter => {
35
+ const url = trimTrailingSlash(baseUrl);
36
+
37
+ return {
38
+ readDocument: async (documentId) =>
39
+ readJson(
40
+ await fetchImpl(`${url}/documents?${documentQuery(documentId)}`),
41
+ ),
42
+ writeDocument: async (input) =>
43
+ readJson(
44
+ await fetchImpl(`${url}/documents?${documentQuery(input.documentId)}`, {
45
+ body: JSON.stringify(input),
46
+ headers: { "Content-Type": "application/json" },
47
+ method: "PUT",
48
+ }),
49
+ ),
50
+ resyncDocument: async (documentId) =>
51
+ readJson(
52
+ await fetchImpl(
53
+ `${url}/documents/resync?${documentQuery(documentId)}`,
54
+ {
55
+ method: "POST",
56
+ },
57
+ ),
58
+ ),
59
+ listDocumentVersions: async (documentId) => {
60
+ const body = await readJson<{ versions: MdKitDocumentVersionSummary[] }>(
61
+ await fetchImpl(`${url}/versions?${documentQuery(documentId)}`),
62
+ );
63
+
64
+ return body.versions;
65
+ },
66
+ readDocumentVersion: async ({ documentId, versionId }) => {
67
+ const response = await fetchImpl(
68
+ `${url}/versions/${encodeURIComponent(versionId)}?${documentQuery(
69
+ documentId,
70
+ )}`,
71
+ );
72
+
73
+ if (response.status === 404) {
74
+ return null;
75
+ }
76
+
77
+ return readJson(response);
78
+ },
79
+ };
80
+ };
@@ -0,0 +1,45 @@
1
+ import type {
2
+ MdKitDocumentSnapshot,
3
+ MdKitDocumentVersionDetail,
4
+ MdKitDocumentVersionSummary,
5
+ MdKitDocumentWriteInput,
6
+ MdKitDocumentWriteResult,
7
+ } from "../document/documentTypes";
8
+
9
+ export type MdKitTransportStore = {
10
+ readDocument(
11
+ documentId: string,
12
+ ): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
13
+ writeDocument(
14
+ input: MdKitDocumentWriteInput,
15
+ ): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
16
+ resyncDocument?(
17
+ documentId: string,
18
+ ): Promise<MdKitDocumentSnapshot> | MdKitDocumentSnapshot;
19
+ listDocumentVersions?(
20
+ documentId: string,
21
+ ): Promise<MdKitDocumentVersionSummary[]> | MdKitDocumentVersionSummary[];
22
+ readDocumentVersion?(input: {
23
+ documentId: string;
24
+ versionId: string;
25
+ }):
26
+ | Promise<MdKitDocumentVersionDetail | null>
27
+ | MdKitDocumentVersionDetail
28
+ | null;
29
+ restoreDocumentVersion?(input: {
30
+ documentId: string;
31
+ versionId: string;
32
+ }): Promise<MdKitDocumentWriteResult> | MdKitDocumentWriteResult;
33
+ readCollaborationState?(
34
+ documentName: string,
35
+ ): Promise<Uint8Array | null> | Uint8Array | null;
36
+ writeCollaborationState?(
37
+ documentName: string,
38
+ state: Uint8Array,
39
+ ): Promise<void> | void;
40
+ };
41
+
42
+ export type MdKitRestoreDocumentVersionInput = {
43
+ documentId: string;
44
+ versionId: string;
45
+ };
@@ -0,0 +1,90 @@
1
+ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
2
+ import type {
3
+ MdKitDocumentAdapter,
4
+ MdKitDocumentSnapshot,
5
+ MdKitDocumentVersionDetail,
6
+ MdKitDocumentVersionSummary,
7
+ MdKitDocumentWriteInput,
8
+ MdKitDocumentWriteResult,
9
+ } from "../document/documentTypes";
10
+ import type { MdKitTrpcRouter } from "./trpcServer";
11
+
12
+ export type CreateMdKitTrpcClientAdapterOptions = {
13
+ url: string;
14
+ };
15
+
16
+ type MdKitTrpcQuery<Input, Output> = {
17
+ query(input: Input): Promise<Output>;
18
+ };
19
+
20
+ type MdKitTrpcMutation<Input, Output> = {
21
+ mutate(input: Input): Promise<Output>;
22
+ };
23
+
24
+ type MdKitDocumentInput = {
25
+ documentId: string;
26
+ };
27
+
28
+ type MdKitVersionInput = {
29
+ documentId: string;
30
+ versionId: string;
31
+ };
32
+
33
+ export type MdKitTrpcClient = {
34
+ listDocumentVersions: MdKitTrpcQuery<
35
+ MdKitDocumentInput,
36
+ { versions: MdKitDocumentVersionSummary[] }
37
+ >;
38
+ readDocument: MdKitTrpcQuery<MdKitDocumentInput, MdKitDocumentSnapshot>;
39
+ readDocumentVersion: MdKitTrpcQuery<
40
+ MdKitVersionInput,
41
+ MdKitDocumentVersionDetail | null
42
+ >;
43
+ resyncDocument: MdKitTrpcMutation<MdKitDocumentInput, MdKitDocumentSnapshot>;
44
+ writeDocument: MdKitTrpcMutation<
45
+ MdKitDocumentWriteInput,
46
+ MdKitDocumentWriteResult
47
+ >;
48
+ };
49
+
50
+ export type CreateMdKitTrpcAdapterOptions = {
51
+ client: MdKitTrpcClient;
52
+ };
53
+
54
+ /**
55
+ * Turns an mdkit tRPC client (standalone or a nested sub-client of your app
56
+ * router) into an {@link MdKitDocumentAdapter} for the document hooks.
57
+ */
58
+ export const createMdKitTrpcAdapter = ({
59
+ client,
60
+ }: CreateMdKitTrpcAdapterOptions): MdKitDocumentAdapter => ({
61
+ listDocumentVersions: async (documentId) => {
62
+ const body = await client.listDocumentVersions.query({ documentId });
63
+ return body.versions;
64
+ },
65
+ readDocument: (documentId) => client.readDocument.query({ documentId }),
66
+ readDocumentVersion: (input) => client.readDocumentVersion.query(input),
67
+ resyncDocument: (documentId) => client.resyncDocument.mutate({ documentId }),
68
+ writeDocument: (input) => client.writeDocument.mutate(input),
69
+ });
70
+
71
+ /**
72
+ * Creates a typed tRPC proxy client for the mdkit router at `url`.
73
+ */
74
+ export const createMdKitTrpcClient = ({
75
+ url,
76
+ }: CreateMdKitTrpcClientAdapterOptions) =>
77
+ createTRPCProxyClient<MdKitTrpcRouter>({
78
+ links: [
79
+ httpBatchLink({
80
+ url,
81
+ }),
82
+ ],
83
+ });
84
+
85
+ export const createMdKitTrpcClientAdapter = ({
86
+ url,
87
+ }: CreateMdKitTrpcClientAdapterOptions) =>
88
+ createMdKitTrpcAdapter({
89
+ client: createMdKitTrpcClient({ url }),
90
+ });
@@ -0,0 +1,66 @@
1
+ import { initTRPC } from "@trpc/server";
2
+ import { z } from "zod";
3
+ import type { MdKitTransportStore } from "./store";
4
+
5
+ const t = initTRPC.create();
6
+
7
+ const documentInput = z.object({
8
+ documentId: z.string(),
9
+ });
10
+
11
+ const versionInput = z.object({
12
+ documentId: z.string(),
13
+ versionId: z.string(),
14
+ });
15
+
16
+ const versionToken = z.union([z.string(), z.number()]).nullable();
17
+
18
+ const writeInput = z.object({
19
+ baseVersion: versionToken,
20
+ content: z.string(),
21
+ documentId: z.string(),
22
+ force: z.boolean().optional(),
23
+ });
24
+
25
+ /**
26
+ * Builds a tRPC router exposing document read/write/resync, checkpoint
27
+ * list/read, and restore over a transport store (typically from
28
+ * `createMdKitBackend`). Mount it standalone or nested in an app router.
29
+ */
30
+ export const createMdKitTrpcRouter = (store: MdKitTransportStore) =>
31
+ t.router({
32
+ listDocumentVersions: t.procedure
33
+ .input(documentInput)
34
+ .query(async ({ input }) => ({
35
+ versions: await (store.listDocumentVersions?.(input.documentId) ?? []),
36
+ })),
37
+ readDocument: t.procedure
38
+ .input(documentInput)
39
+ .query(({ input }) => store.readDocument(input.documentId)),
40
+ readDocumentVersion: t.procedure.input(versionInput).query(
41
+ ({ input }) =>
42
+ store.readDocumentVersion?.({
43
+ documentId: input.documentId,
44
+ versionId: input.versionId,
45
+ }) ?? null,
46
+ ),
47
+ resyncDocument: t.procedure
48
+ .input(documentInput)
49
+ .mutation(({ input }) =>
50
+ (store.resyncDocument ?? store.readDocument)(input.documentId),
51
+ ),
52
+ restoreDocumentVersion: t.procedure
53
+ .input(versionInput)
54
+ .mutation(({ input }) => {
55
+ if (!store.restoreDocumentVersion) {
56
+ throw new Error("Version restore is not supported");
57
+ }
58
+
59
+ return store.restoreDocumentVersion(input);
60
+ }),
61
+ writeDocument: t.procedure
62
+ .input(writeInput)
63
+ .mutation(({ input }) => store.writeDocument(input)),
64
+ });
65
+
66
+ export type MdKitTrpcRouter = ReturnType<typeof createMdKitTrpcRouter>;
@@ -0,0 +1,11 @@
1
+ export {
2
+ createMdKitTrpcAdapter,
3
+ createMdKitTrpcClient,
4
+ createMdKitTrpcClientAdapter,
5
+ } from "../transport/trpcClient";
6
+ export type {
7
+ CreateMdKitTrpcAdapterOptions,
8
+ CreateMdKitTrpcClientAdapterOptions,
9
+ MdKitTrpcClient,
10
+ } from "../transport/trpcClient";
11
+ export type { MdKitTrpcRouter } from "../transport/trpcServer";
@@ -0,0 +1,12 @@
1
+ export { createMdKitBackend } from "../transport/backend";
2
+ export { createMdKitTrpcRouter } from "../transport/trpcServer";
3
+ export type {
4
+ CreateMdKitBackendOptions,
5
+ MdKitBackendStore,
6
+ MdKitCreateCheckpointInput,
7
+ } from "../transport/backend";
8
+ export type { MdKitTrpcRouter } from "../transport/trpcServer";
9
+ export type {
10
+ MdKitRestoreDocumentVersionInput,
11
+ MdKitTransportStore,
12
+ } from "../transport/store";
package/src/trpc.ts ADDED
@@ -0,0 +1,11 @@
1
+ export {
2
+ createMdKitTrpcAdapter,
3
+ createMdKitTrpcClient,
4
+ createMdKitTrpcClientAdapter,
5
+ } from "./transport/trpcClient";
6
+ export type {
7
+ CreateMdKitTrpcAdapterOptions,
8
+ CreateMdKitTrpcClientAdapterOptions,
9
+ MdKitTrpcClient,
10
+ } from "./transport/trpcClient";
11
+ export type { MdKitTrpcRouter } from "./transport/trpcServer";
@@ -0,0 +1,3 @@
1
+ export const joinClassNames = (
2
+ ...values: Array<string | false | null | undefined>
3
+ ) => values.filter(Boolean).join(" ");
@@ -0,0 +1,146 @@
1
+ import { useState } from "react";
2
+ import type { MdKitDocumentVersionDetail } from "../document/documentTypes";
3
+ import type { MdKitDocumentVersionsController } from "./useMdKitDocumentVersions";
4
+ import { joinClassNames } from "../ui/joinClassNames";
5
+
6
+ export type VersionHistoryPanelProps = {
7
+ className?: string;
8
+ controller: MdKitDocumentVersionsController;
9
+ onRestoreVersion?: (
10
+ version: MdKitDocumentVersionDetail,
11
+ ) => Promise<void> | void;
12
+ title?: string;
13
+ };
14
+
15
+ const getVersionLabel = (
16
+ version: Pick<MdKitDocumentVersionDetail, "id" | "label" | "version">,
17
+ ) => {
18
+ if (version.label) {
19
+ return version.label;
20
+ }
21
+
22
+ if (version.version != null) {
23
+ return `Version ${String(version.version)}`;
24
+ }
25
+
26
+ return version.id.slice(0, 8);
27
+ };
28
+
29
+ /**
30
+ * Renders checkpoint history from {@link useMdKitDocumentVersions} and invokes
31
+ * the host's restore handler for a chosen checkpoint.
32
+ */
33
+ export const VersionHistoryPanel = ({
34
+ className,
35
+ controller,
36
+ onRestoreVersion,
37
+ title = "Version history",
38
+ }: VersionHistoryPanelProps) => {
39
+ const [isRestoring, setIsRestoring] = useState(false);
40
+
41
+ const restoreSelectedVersion = async () => {
42
+ if (!controller.selectedVersion || !onRestoreVersion) {
43
+ return;
44
+ }
45
+
46
+ setIsRestoring(true);
47
+
48
+ try {
49
+ await onRestoreVersion(controller.selectedVersion);
50
+ } finally {
51
+ setIsRestoring(false);
52
+ }
53
+ };
54
+
55
+ return (
56
+ <aside className={joinClassNames("mp-lb-mdkit-version-history-panel", className)}>
57
+ <div className="mp-lb-mdkit-version-history-header">
58
+ <div>
59
+ <h2 className="mp-lb-mdkit-version-history-title">{title}</h2>
60
+ <p className="mp-lb-mdkit-version-history-subtitle">
61
+ Browse saved revisions and restore one when you need it.
62
+ </p>
63
+ </div>
64
+ </div>
65
+ {!controller.hasVersioning ? (
66
+ <div className="mp-lb-mdkit-version-history-empty">
67
+ This adapter does not expose version history.
68
+ </div>
69
+ ) : null}
70
+ {controller.error ? (
71
+ <div className="mp-lb-mdkit-version-history-error">{controller.error}</div>
72
+ ) : null}
73
+ {controller.hasVersioning ? (
74
+ <div className="mp-lb-mdkit-version-history-layout">
75
+ <div className="mp-lb-mdkit-version-history-list" role="list">
76
+ {controller.versions.length === 0 ? (
77
+ <div className="mp-lb-mdkit-version-history-empty">
78
+ No revisions have been recorded for this document yet.
79
+ </div>
80
+ ) : (
81
+ controller.versions.map((version) => (
82
+ <button
83
+ key={version.id}
84
+ type="button"
85
+ className={joinClassNames(
86
+ "mp-lb-mdkit-version-history-item",
87
+ controller.selectedVersionId === version.id &&
88
+ "mp-lb-mdkit-version-history-item-active",
89
+ )}
90
+ onClick={() => void controller.openVersion(version.id)}
91
+ >
92
+ <span className="mp-lb-mdkit-version-history-item-title">
93
+ {getVersionLabel(version)}
94
+ </span>
95
+ <span className="mp-lb-mdkit-version-history-item-meta">
96
+ {new Date(version.createdAt).toLocaleString()}
97
+ </span>
98
+ </button>
99
+ ))
100
+ )}
101
+ </div>
102
+ <div className="mp-lb-mdkit-version-history-preview">
103
+ {controller.selectedVersion ? (
104
+ <>
105
+ <div className="mp-lb-mdkit-version-history-preview-header">
106
+ <div>
107
+ <h3 className="mp-lb-mdkit-version-history-preview-title">
108
+ {getVersionLabel(controller.selectedVersion)}
109
+ </h3>
110
+ <p className="mp-lb-mdkit-version-history-item-meta">
111
+ {new Date(
112
+ controller.selectedVersion.createdAt,
113
+ ).toLocaleString()}
114
+ </p>
115
+ </div>
116
+ {onRestoreVersion ? (
117
+ <button
118
+ type="button"
119
+ className="mp-lb-mdkit-panel-primary-action"
120
+ disabled={isRestoring}
121
+ onClick={() => void restoreSelectedVersion()}
122
+ >
123
+ {isRestoring ? "Restoring..." : "Restore"}
124
+ </button>
125
+ ) : null}
126
+ </div>
127
+ <pre className="mp-lb-mdkit-version-history-code">
128
+ {controller.selectedVersion.content}
129
+ </pre>
130
+ </>
131
+ ) : (
132
+ <div className="mp-lb-mdkit-version-history-empty">
133
+ Select a saved revision to preview it here.
134
+ </div>
135
+ )}
136
+ </div>
137
+ </div>
138
+ ) : null}
139
+ {controller.isLoading ? (
140
+ <div className="mp-lb-mdkit-version-history-meta">
141
+ Loading version data...
142
+ </div>
143
+ ) : null}
144
+ </aside>
145
+ );
146
+ };