@nkzw/codiff-service 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs ADDED
@@ -0,0 +1,6 @@
1
+ import { plan, planRelations, shareCommentMessage, shareCommentMessageRelations, shareCommentThread, shareCommentThreadKind, shareCommentThreadRelations, shareCommentThreadStatus, shareDailyUsage, shareDailyUsageRelations, shareKind, uploadIntent, user, userRelations, walkthrough, walkthroughFile, walkthroughFileRelations, walkthroughRelations } from "./schema.mjs";
2
+ import { and, createDb, db, eq, gte, runDatabaseBatches, sql, withDatabase } from "./db.mjs";
3
+ import { getAuthenticatedShareUser, getRepositoryMetadata, handleSharingApiRequest, parseGitPullRequestUrl, parseShareUpload, shareUserFromAuthValue } from "./api.mjs";
4
+ import { SharingRoot, getSessionUserId, planDataView, shareCommentMessageDataView, shareCommentThreadDataView, shareStatsDataView, shareStatsDayDataView, uploadIntentDataView, uploadIntentSecretHeader, walkthroughDataView } from "./views.mjs";
5
+ import { createSharingFateServer, shareCommentAnchorSchema, shareCommentTargetSchema } from "./fate.mjs";
6
+ export { SharingRoot, and, createDb, createSharingFateServer, db, eq, getAuthenticatedShareUser, getRepositoryMetadata, getSessionUserId, gte, handleSharingApiRequest, parseGitPullRequestUrl, parseShareUpload, plan, planDataView, planRelations, runDatabaseBatches, shareCommentAnchorSchema, shareCommentMessage, shareCommentMessageDataView, shareCommentMessageRelations, shareCommentTargetSchema, shareCommentThread, shareCommentThreadDataView, shareCommentThreadKind, shareCommentThreadRelations, shareCommentThreadStatus, shareDailyUsage, shareDailyUsageRelations, shareKind, shareStatsDataView, shareStatsDayDataView, shareUserFromAuthValue, sql, uploadIntent, uploadIntentDataView, uploadIntentSecretHeader, user, userRelations, walkthrough, walkthroughDataView, walkthroughFile, walkthroughFileRelations, walkthroughRelations, withDatabase };
package/dist/react.mjs ADDED
@@ -0,0 +1,62 @@
1
+ import { PlanReviewSurface, ReviewSurface } from "@nkzw/codiff-core/react";
2
+ import { jsx } from "react/jsx-runtime";
3
+ //#region react.tsx
4
+ const asRecord = (value) => value && typeof value === "object" ? value : null;
5
+ const submittedShareCommentMessage = (value) => {
6
+ const message = asRecord(value);
7
+ if (!message || typeof message.authorName !== "string" || typeof message.body !== "string" || typeof message.canEdit !== "boolean" || typeof message.createdAt !== "string" || typeof message.id !== "string" || typeof message.threadId !== "string") throw new Error("Unable to load the submitted walkthrough comment.");
8
+ return {
9
+ authorImage: typeof message.authorImage === "string" ? message.authorImage : null,
10
+ authorName: message.authorName,
11
+ authorUsername: typeof message.authorUsername === "string" ? message.authorUsername : null,
12
+ body: message.body,
13
+ canEdit: message.canEdit,
14
+ createdAt: message.createdAt,
15
+ id: message.id,
16
+ threadId: message.threadId
17
+ };
18
+ };
19
+ const toSubmittedReviewComment = (comment, message, threadId, canResolveThread) => ({
20
+ ...comment,
21
+ author: {
22
+ ...message.authorImage ? { avatarUrl: message.authorImage } : {},
23
+ login: message.authorUsername ?? message.authorName,
24
+ name: message.authorName
25
+ },
26
+ body: message.body,
27
+ ...message.canEdit ? {
28
+ canDelete: true,
29
+ canEdit: true
30
+ } : {},
31
+ ...canResolveThread ? { canResolveThread: true } : {},
32
+ id: message.id,
33
+ submittedAt: message.createdAt,
34
+ threadId
35
+ });
36
+ const resolveSubmittedShareReply = ({ canResolveThread, comment, result }) => toSubmittedReviewComment(comment, submittedShareCommentMessage(result), comment.threadId, canResolveThread);
37
+ const resolveSubmittedShareThread = ({ canResolveThread, comment, result }) => {
38
+ const thread = asRecord(result);
39
+ const messages = thread ? asRecord(thread.messages) : null;
40
+ const firstItem = messages && Array.isArray(messages.items) ? asRecord(messages.items[0]) : null;
41
+ if (!thread || typeof thread.id !== "string" || !firstItem) throw new Error("Unable to load the submitted walkthrough comment.");
42
+ return toSubmittedReviewComment(comment, submittedShareCommentMessage(firstItem.node), thread.id, canResolveThread);
43
+ };
44
+ function SharedPlanApp({ providerLabel, ...props }) {
45
+ return /* @__PURE__ */ jsx(PlanReviewSurface, {
46
+ ...props,
47
+ signInLabel: `Sign in with ${providerLabel} to comment`
48
+ });
49
+ }
50
+ function SharedWalkthroughApp({ commenting, gitIdentity, onDeleteShare, providerLabel, settingsBar, snapshot }) {
51
+ return /* @__PURE__ */ jsx(ReviewSurface, {
52
+ commenting,
53
+ gitIdentity,
54
+ onDeleteShare,
55
+ providerLabel,
56
+ settingsBar,
57
+ signInLabel: `Sign in with ${providerLabel} to comment`,
58
+ snapshot
59
+ });
60
+ }
61
+ //#endregion
62
+ export { SharedPlanApp, SharedWalkthroughApp, resolveSubmittedShareReply, resolveSubmittedShareThread };
@@ -0,0 +1,219 @@
1
+ import { relations, sql } from "drizzle-orm";
2
+ import { check, index, integer, primaryKey, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core";
3
+ //#region schema.ts
4
+ const idColumn = () => text().notNull().$defaultFn(() => crypto.randomUUID());
5
+ const timestampColumn = () => integer({ mode: "timestamp" }).notNull().default(sql`(unixepoch())`);
6
+ const nullableTimestampColumn = () => integer({ mode: "timestamp" });
7
+ const shareCommentThreadKind = [
8
+ "plan",
9
+ "walkthrough-diff",
10
+ "walkthrough-general"
11
+ ];
12
+ const shareCommentThreadStatus = ["open", "resolved"];
13
+ const shareKind = ["plan", "walkthrough"];
14
+ const user = sqliteTable("user", {
15
+ banExpires: nullableTimestampColumn(),
16
+ banned: integer({ mode: "boolean" }),
17
+ banReason: text(),
18
+ createdAt: timestampColumn(),
19
+ displayUsername: text(),
20
+ email: text().notNull(),
21
+ emailVerified: integer({ mode: "boolean" }).notNull().default(false),
22
+ id: idColumn().primaryKey(),
23
+ image: text(),
24
+ name: text().notNull(),
25
+ password: text(),
26
+ role: text().notNull().default("user"),
27
+ updatedAt: timestampColumn(),
28
+ username: text()
29
+ }, (table) => [
30
+ uniqueIndex("user_email_key").on(table.email),
31
+ uniqueIndex("user_username_key").on(table.username),
32
+ index("user_id_idx").on(table.id)
33
+ ]);
34
+ const walkthrough = sqliteTable("Walkthrough", {
35
+ branch: text(),
36
+ byteSize: integer().notNull(),
37
+ codiffVersion: text().notNull(),
38
+ commentsImportedAt: nullableTimestampColumn(),
39
+ createdAt: timestampColumn(),
40
+ description: text(),
41
+ filesIndexedAt: nullableTimestampColumn(),
42
+ id: idColumn().primaryKey(),
43
+ objectKey: text().notNull(),
44
+ pullRequestNumber: integer(),
45
+ pullRequestTitle: text(),
46
+ pullRequestUrl: text(),
47
+ repositoryHost: text(),
48
+ repositoryName: text(),
49
+ repositoryOwner: text(),
50
+ repositoryUrl: text(),
51
+ schemaVersion: integer().notNull(),
52
+ sha256: text().notNull(),
53
+ sharedByEmail: text(),
54
+ sharedByName: text(),
55
+ sharedByUserId: text().references(() => user.id, { onDelete: "set null" }),
56
+ slug: text().notNull(),
57
+ sourceType: text().notNull(),
58
+ title: text().notNull(),
59
+ updatedAt: timestampColumn()
60
+ }, (table) => [
61
+ uniqueIndex("Walkthrough_slug_key").on(table.slug),
62
+ index("Walkthrough_sharedByEmail_idx").on(table.sharedByEmail),
63
+ index("Walkthrough_sharedByUserId_idx").on(table.sharedByUserId),
64
+ index("Walkthrough_repository_idx").on(table.repositoryOwner, table.repositoryName)
65
+ ]);
66
+ const walkthroughFile = sqliteTable("WalkthroughFile", {
67
+ path: text().notNull(),
68
+ walkthroughId: text().notNull().references(() => walkthrough.id, { onDelete: "cascade" })
69
+ }, (table) => [primaryKey({
70
+ columns: [table.walkthroughId, table.path],
71
+ name: "WalkthroughFile_walkthroughId_path_pkey"
72
+ })]);
73
+ const plan = sqliteTable("Plan", {
74
+ agent: text(),
75
+ byteSize: integer().notNull(),
76
+ codiffVersion: text().notNull(),
77
+ commentsImportedAt: nullableTimestampColumn(),
78
+ createdAt: timestampColumn(),
79
+ id: idColumn().primaryKey(),
80
+ objectKey: text().notNull(),
81
+ schemaVersion: integer().notNull(),
82
+ sessionId: text(),
83
+ sha256: text().notNull(),
84
+ sharedByEmail: text(),
85
+ sharedByName: text(),
86
+ sharedByUserId: text().references(() => user.id, { onDelete: "set null" }),
87
+ slug: text().notNull(),
88
+ sourceFileName: text().notNull(),
89
+ title: text().notNull(),
90
+ updatedAt: timestampColumn()
91
+ }, (table) => [
92
+ uniqueIndex("Plan_slug_key").on(table.slug),
93
+ index("Plan_sharedByEmail_idx").on(table.sharedByEmail),
94
+ index("Plan_sharedByUserId_idx").on(table.sharedByUserId)
95
+ ]);
96
+ const uploadIntent = sqliteTable("UploadIntent", {
97
+ claimedAt: nullableTimestampColumn(),
98
+ code: text().notNull(),
99
+ createdAt: timestampColumn(),
100
+ expiresAt: integer({ mode: "timestamp" }).notNull(),
101
+ id: idColumn().primaryKey(),
102
+ secretHash: text().notNull(),
103
+ sharedByUserId: text().references(() => user.id, { onDelete: "set null" }),
104
+ shareKind: text({ enum: shareKind }).notNull().default("walkthrough"),
105
+ status: text({ enum: [
106
+ "pending",
107
+ "claimed",
108
+ "uploaded"
109
+ ] }).notNull().default("pending"),
110
+ updatedAt: timestampColumn(),
111
+ uploadTokenHash: text(),
112
+ walkthroughSlug: text()
113
+ }, (table) => [uniqueIndex("UploadIntent_code_key").on(table.code), index("UploadIntent_sharedByUserId_idx").on(table.sharedByUserId)]);
114
+ const shareCommentThread = sqliteTable("ShareCommentThread", {
115
+ anchorJson: text(),
116
+ createdAt: timestampColumn(),
117
+ filePath: text(),
118
+ id: idColumn().primaryKey(),
119
+ kind: text({ enum: shareCommentThreadKind }).notNull(),
120
+ lineNumber: integer(),
121
+ planId: text().references(() => plan.id, { onDelete: "cascade" }),
122
+ resolvedAt: nullableTimestampColumn(),
123
+ sectionId: text(),
124
+ side: text({ enum: ["additions", "deletions"] }),
125
+ sourceThreadId: text(),
126
+ startLineNumber: integer(),
127
+ startSide: text({ enum: ["additions", "deletions"] }),
128
+ status: text({ enum: shareCommentThreadStatus }).notNull().default("open"),
129
+ updatedAt: timestampColumn(),
130
+ walkthroughId: text().references(() => walkthrough.id, { onDelete: "cascade" })
131
+ }, (table) => [
132
+ check("ShareCommentThread_parent_check", sql`(${table.planId} is not null and ${table.walkthroughId} is null) or (${table.planId} is null and ${table.walkthroughId} is not null)`),
133
+ index("ShareCommentThread_planId_createdAt_idx").on(table.planId, table.createdAt),
134
+ index("ShareCommentThread_walkthroughId_createdAt_idx").on(table.walkthroughId, table.createdAt),
135
+ uniqueIndex("ShareCommentThread_planId_sourceThreadId_key").on(table.planId, table.sourceThreadId),
136
+ uniqueIndex("ShareCommentThread_walkthroughId_sourceThreadId_key").on(table.walkthroughId, table.sourceThreadId)
137
+ ]);
138
+ const shareCommentMessage = sqliteTable("ShareCommentMessage", {
139
+ authorImage: text(),
140
+ authorName: text().notNull(),
141
+ authorUserId: text().references(() => user.id, { onDelete: "set null" }),
142
+ authorUsername: text(),
143
+ body: text().notNull(),
144
+ createdAt: timestampColumn(),
145
+ id: idColumn().primaryKey(),
146
+ sourceMessageId: text(),
147
+ threadId: text().notNull().references(() => shareCommentThread.id, { onDelete: "cascade" }),
148
+ updatedAt: timestampColumn()
149
+ }, (table) => [
150
+ index("ShareCommentMessage_authorUserId_idx").on(table.authorUserId),
151
+ index("ShareCommentMessage_threadId_createdAt_idx").on(table.threadId, table.createdAt),
152
+ uniqueIndex("ShareCommentMessage_threadId_sourceMessageId_key").on(table.threadId, table.sourceMessageId)
153
+ ]);
154
+ const shareDailyUsage = sqliteTable("ShareDailyUsage", {
155
+ date: text().notNull(),
156
+ planCount: integer().notNull().default(0),
157
+ userId: text().notNull().references(() => user.id, { onDelete: "cascade" }),
158
+ walkthroughCount: integer().notNull().default(0)
159
+ }, (table) => [primaryKey({
160
+ columns: [table.userId, table.date],
161
+ name: "ShareDailyUsage_userId_date_pkey"
162
+ })]);
163
+ const userRelations = relations(user, ({ many }) => ({
164
+ shareCommentMessages: many(shareCommentMessage),
165
+ shareDailyUsage: many(shareDailyUsage)
166
+ }));
167
+ const walkthroughRelations = relations(walkthrough, ({ many }) => ({
168
+ commentThreads: many(shareCommentThread),
169
+ files: many(walkthroughFile)
170
+ }));
171
+ const walkthroughFileRelations = relations(walkthroughFile, ({ one }) => ({ walkthrough: one(walkthrough, {
172
+ fields: [walkthroughFile.walkthroughId],
173
+ references: [walkthrough.id]
174
+ }) }));
175
+ const planRelations = relations(plan, ({ many }) => ({ commentThreads: many(shareCommentThread) }));
176
+ const shareCommentThreadRelations = relations(shareCommentThread, ({ many, one }) => ({
177
+ messages: many(shareCommentMessage),
178
+ plan: one(plan, {
179
+ fields: [shareCommentThread.planId],
180
+ references: [plan.id]
181
+ }),
182
+ walkthrough: one(walkthrough, {
183
+ fields: [shareCommentThread.walkthroughId],
184
+ references: [walkthrough.id]
185
+ })
186
+ }));
187
+ const shareCommentMessageRelations = relations(shareCommentMessage, ({ one }) => ({
188
+ author: one(user, {
189
+ fields: [shareCommentMessage.authorUserId],
190
+ references: [user.id]
191
+ }),
192
+ thread: one(shareCommentThread, {
193
+ fields: [shareCommentMessage.threadId],
194
+ references: [shareCommentThread.id]
195
+ })
196
+ }));
197
+ const shareDailyUsageRelations = relations(shareDailyUsage, ({ one }) => ({ user: one(user, {
198
+ fields: [shareDailyUsage.userId],
199
+ references: [user.id]
200
+ }) }));
201
+ const schema = {
202
+ plan,
203
+ planRelations,
204
+ shareCommentMessage,
205
+ shareCommentMessageRelations,
206
+ shareCommentThread,
207
+ shareCommentThreadRelations,
208
+ shareDailyUsage,
209
+ shareDailyUsageRelations,
210
+ uploadIntent,
211
+ user,
212
+ userRelations,
213
+ walkthrough,
214
+ walkthroughFile,
215
+ walkthroughFileRelations,
216
+ walkthroughRelations
217
+ };
218
+ //#endregion
219
+ export { schema as default, plan, planRelations, shareCommentMessage, shareCommentMessageRelations, shareCommentThread, shareCommentThreadKind, shareCommentThreadRelations, shareCommentThreadStatus, shareDailyUsage, shareDailyUsageRelations, shareKind, uploadIntent, user, userRelations, walkthrough, walkthroughFile, walkthroughFileRelations, walkthroughRelations };
@@ -0,0 +1,48 @@
1
+ //#region upload-intent.ts
2
+ const encoder = new TextEncoder();
3
+ const tokenVersion = "v1";
4
+ const base64UrlEncode = (value) => btoa(String.fromCharCode(...value)).replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
5
+ const base64UrlDecode = (value) => {
6
+ const padding = "=".repeat((4 - value.length % 4) % 4);
7
+ const decoded = atob(value.replaceAll("-", "+").replaceAll("_", "/") + padding);
8
+ return Uint8Array.from(decoded, (character) => character.charCodeAt(0));
9
+ };
10
+ const signingKey = (env) => {
11
+ const secret = env.BETTER_AUTH_SECRET?.trim();
12
+ if (!secret) throw new Error("Codiff sharing requires BETTER_AUTH_SECRET.");
13
+ return crypto.subtle.importKey("raw", encoder.encode(secret), {
14
+ hash: "SHA-256",
15
+ name: "HMAC"
16
+ }, false, ["sign", "verify"]);
17
+ };
18
+ const signedValue = (code, payload) => `${code}.${payload}`;
19
+ const createUploadIntentSecret = async (env, code, expiresAt) => {
20
+ const nonce = /* @__PURE__ */ new Uint8Array(24);
21
+ crypto.getRandomValues(nonce);
22
+ const payload = [
23
+ tokenVersion,
24
+ expiresAt.getTime().toString(36),
25
+ base64UrlEncode(nonce)
26
+ ].join(".");
27
+ const signature = await crypto.subtle.sign("HMAC", await signingKey(env), encoder.encode(signedValue(code, payload)));
28
+ return `${payload}.${base64UrlEncode(new Uint8Array(signature))}`;
29
+ };
30
+ const hashUploadIntentSecret = async (secret) => {
31
+ const digest = await crypto.subtle.digest("SHA-256", encoder.encode(secret));
32
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
33
+ };
34
+ const verifyUploadIntentSecret = async (env, code, secret) => {
35
+ const parts = secret.split(".");
36
+ if (parts.length !== 4 || parts[0] !== tokenVersion) return null;
37
+ const [version, encodedExpiresAt, nonce, encodedSignature] = parts;
38
+ const expiresAtMs = Number.parseInt(encodedExpiresAt, 36);
39
+ if (version !== tokenVersion || !Number.isSafeInteger(expiresAtMs) || expiresAtMs <= 0 || !nonce || !encodedSignature) return null;
40
+ try {
41
+ const payload = `${version}.${encodedExpiresAt}.${nonce}`;
42
+ return await crypto.subtle.verify("HMAC", await signingKey(env), base64UrlDecode(encodedSignature), encoder.encode(signedValue(code, payload))) ? { expiresAt: new Date(expiresAtMs) } : null;
43
+ } catch {
44
+ return null;
45
+ }
46
+ };
47
+ //#endregion
48
+ export { createUploadIntentSecret, hashUploadIntentSecret, verifyUploadIntentSecret };
package/dist/views.mjs ADDED
@@ -0,0 +1,142 @@
1
+ import { FateRequestError, computed, dataView, field, list } from "@nkzw/fate/server";
2
+ //#region views.ts
3
+ const uploadIntentSecretHeader = "x-codiff-upload-secret";
4
+ const isRecord = (value) => value !== null && typeof value === "object";
5
+ const getSessionUserId = (value) => {
6
+ const result = isRecord(value) && isRecord(value.response) ? value.response : value;
7
+ const sessionUser = isRecord(result) ? result.user : null;
8
+ return isRecord(sessionUser) && typeof sessionUser.id === "string" && sessionUser.id ? sessionUser.id : null;
9
+ };
10
+ const sessionUserIds = /* @__PURE__ */ new WeakMap();
11
+ const readContextSessionUserId = (context) => {
12
+ if (!context.auth) return Promise.resolve(null);
13
+ const existing = sessionUserIds.get(context.request);
14
+ if (existing) return existing;
15
+ const result = context.auth.api.getSession({
16
+ asResponse: false,
17
+ headers: context.request.headers
18
+ }).then(getSessionUserId);
19
+ sessionUserIds.set(context.request, result);
20
+ return result;
21
+ };
22
+ const canManageShare = () => computed({
23
+ resolve: async (_record, values, context) => Boolean(values.sharedByUserId && context?.auth && await readContextSessionUserId(context) === values.sharedByUserId),
24
+ select: { sharedByUserId: field("sharedByUserId") }
25
+ });
26
+ const canEditShareComment = () => computed({
27
+ resolve: async (_record, values, context) => Boolean(values.authorUserId && context?.auth && await readContextSessionUserId(context) === values.authorUserId),
28
+ select: { authorUserId: field("authorUserId") }
29
+ });
30
+ const sha256 = async (value) => {
31
+ const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(value));
32
+ return [...new Uint8Array(digest)].map((byte) => byte.toString(16).padStart(2, "0")).join("");
33
+ };
34
+ const protectedUploadIntentField = (fieldName) => computed({
35
+ resolve: async (_intent, values, context) => {
36
+ const secret = context?.request.headers.get(uploadIntentSecretHeader);
37
+ if (!secret || values.secretHash !== await sha256(secret)) throw new FateRequestError("NOT_FOUND", "Upload intent not found.");
38
+ return values.value;
39
+ },
40
+ select: {
41
+ secretHash: field("secretHash"),
42
+ value: field(fieldName)
43
+ }
44
+ });
45
+ const uploadIntentDataView = dataView("UploadIntent")({
46
+ expiresAt: protectedUploadIntentField("expiresAt"),
47
+ id: true,
48
+ shareKind: protectedUploadIntentField("shareKind"),
49
+ status: protectedUploadIntentField("status"),
50
+ walkthroughSlug: protectedUploadIntentField("walkthroughSlug")
51
+ });
52
+ const shareCommentMessageDataView = dataView("ShareCommentMessage")({
53
+ authorImage: true,
54
+ authorName: true,
55
+ authorUsername: true,
56
+ body: true,
57
+ canEdit: canEditShareComment(),
58
+ createdAt: true,
59
+ id: true,
60
+ threadId: true,
61
+ updatedAt: true
62
+ });
63
+ const shareCommentThreadDataView = dataView("ShareCommentThread")({
64
+ anchorJson: true,
65
+ createdAt: true,
66
+ filePath: true,
67
+ id: true,
68
+ kind: true,
69
+ lineNumber: true,
70
+ messages: list(shareCommentMessageDataView, { orderBy: [{ createdAt: "asc" }, { id: "asc" }] }),
71
+ planId: true,
72
+ resolvedAt: true,
73
+ sectionId: true,
74
+ side: true,
75
+ startLineNumber: true,
76
+ startSide: true,
77
+ status: true,
78
+ updatedAt: true,
79
+ walkthroughId: true
80
+ });
81
+ const walkthroughDataView = dataView("Walkthrough")({
82
+ branch: true,
83
+ byteSize: true,
84
+ canDelete: canManageShare(),
85
+ canResolveComments: canManageShare(),
86
+ codiffVersion: true,
87
+ commentThreads: list(shareCommentThreadDataView, { orderBy: [{ createdAt: "asc" }, { id: "asc" }] }),
88
+ createdAt: true,
89
+ description: true,
90
+ id: true,
91
+ pullRequestNumber: true,
92
+ pullRequestTitle: true,
93
+ pullRequestUrl: true,
94
+ repositoryHost: true,
95
+ repositoryName: true,
96
+ repositoryOwner: true,
97
+ repositoryUrl: true,
98
+ schemaVersion: true,
99
+ sha256: true,
100
+ slug: true,
101
+ sourceType: true,
102
+ title: true,
103
+ updatedAt: true
104
+ });
105
+ const planDataView = dataView("Plan")({
106
+ agent: true,
107
+ byteSize: true,
108
+ canDelete: canManageShare(),
109
+ canResolveComments: canManageShare(),
110
+ codiffVersion: true,
111
+ commentThreads: list(shareCommentThreadDataView, { orderBy: [{ createdAt: "asc" }, { id: "asc" }] }),
112
+ createdAt: true,
113
+ id: true,
114
+ schemaVersion: true,
115
+ slug: true,
116
+ sourceFileName: true,
117
+ title: true,
118
+ updatedAt: true
119
+ });
120
+ const shareStatsDayDataView = dataView("ShareStatsDay")({
121
+ date: true,
122
+ id: true,
123
+ mergeRequestWalkthroughs: true,
124
+ plans: true,
125
+ walkthroughs: true
126
+ });
127
+ const shareStatsDataView = dataView("ShareStats")({
128
+ days: list(shareStatsDayDataView),
129
+ id: true,
130
+ maxDailyShares: true,
131
+ totalMergeRequestWalkthroughs: true,
132
+ totalPlans: true,
133
+ totalWalkthroughs: true
134
+ });
135
+ const SharingRoot = {
136
+ planBySlug: planDataView,
137
+ sharingStats: shareStatsDataView,
138
+ uploadIntentByCode: uploadIntentDataView,
139
+ walkthroughBySlug: walkthroughDataView
140
+ };
141
+ //#endregion
142
+ export { SharingRoot, getSessionUserId, planDataView, shareCommentMessageDataView, shareCommentThreadDataView, shareStatsDataView, shareStatsDayDataView, uploadIntentDataView, uploadIntentSecretHeader, walkthroughDataView };
package/fate.test.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { createLiveEventBus } from '@nkzw/fate/server';
2
+ import { expect, test } from 'vite-plus/test';
3
+ import { createSharingFateServer } from './fate.ts';
4
+
5
+ const fateServer = createSharingFateServer({
6
+ live: createLiveEventBus(),
7
+ providerLabel: 'GitHub',
8
+ });
9
+
10
+ test('enables live subscriptions for every shared service entity', () => {
11
+ expect(fateServer.manifest.live).toEqual(fateServer.manifest.types);
12
+ expect(Object.keys(fateServer.manifest.live)).toEqual([
13
+ 'Plan',
14
+ 'ShareCommentMessage',
15
+ 'ShareCommentThread',
16
+ 'ShareStats',
17
+ 'ShareStatsDay',
18
+ 'UploadIntent',
19
+ 'Walkthrough',
20
+ ]);
21
+ });