@agent-native/core 0.114.0 → 0.114.1
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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +9 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/build-compatibility.ts +77 -0
- package/corpus/core/src/client/route-chunk-recovery/index.ts +1 -0
- package/corpus/core/src/client/use-action.ts +32 -1
- package/corpus/core/src/deploy/build.ts +4 -1
- package/corpus/core/src/org/context.ts +56 -30
- package/corpus/core/src/server/action-routes.ts +52 -2
- package/corpus/core/src/server/auth-plugin.ts +9 -4
- package/corpus/core/src/server/better-auth-instance.ts +5 -1
- package/corpus/core/src/shared/mcp-embed-headers.ts +1 -1
- package/corpus/core/src/vite/client.ts +17 -0
- package/corpus/templates/content/AGENTS.md +2 -0
- package/corpus/templates/content/actions/_content-database-personal-view.ts +49 -5
- package/corpus/templates/content/actions/_content-document-access.ts +21 -0
- package/corpus/templates/content/actions/_content-favorites.ts +91 -0
- package/corpus/templates/content/actions/_content-files.ts +79 -23
- package/corpus/templates/content/actions/_content-sidebar-state.ts +24 -0
- package/corpus/templates/content/actions/_content-space-access.ts +15 -17
- package/corpus/templates/content/actions/_content-space-catalog-guards.ts +20 -0
- package/corpus/templates/content/actions/_content-spaces.ts +364 -167
- package/corpus/templates/content/actions/_database-source-utils.ts +8 -0
- package/corpus/templates/content/actions/_database-utils.ts +249 -15
- package/corpus/templates/content/actions/_delete-content-space.ts +84 -0
- package/corpus/templates/content/actions/_files-system-properties.ts +499 -0
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -1
- package/corpus/templates/content/actions/_property-utils.ts +18 -9
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +3 -0
- package/corpus/templates/content/actions/configure-document-property.ts +3 -0
- package/corpus/templates/content/actions/create-content-database.ts +2 -2
- package/corpus/templates/content/actions/create-content-space.ts +34 -0
- package/corpus/templates/content/actions/create-document.ts +4 -1
- package/corpus/templates/content/actions/delete-content-space.ts +19 -0
- package/corpus/templates/content/actions/delete-database-items.ts +28 -1
- package/corpus/templates/content/actions/delete-document-property.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +39 -2
- package/corpus/templates/content/actions/duplicate-database-item.ts +6 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +6 -0
- package/corpus/templates/content/actions/duplicate-document-property.ts +3 -0
- package/corpus/templates/content/actions/get-content-database.ts +11 -2
- package/corpus/templates/content/actions/get-content-sidebar-state.ts +23 -0
- package/corpus/templates/content/actions/get-document.ts +32 -7
- package/corpus/templates/content/actions/import-content-source.ts +33 -1
- package/corpus/templates/content/actions/list-content-spaces.ts +34 -2
- package/corpus/templates/content/actions/list-documents.ts +39 -7
- package/corpus/templates/content/actions/reorder-document-property.ts +9 -0
- package/corpus/templates/content/actions/set-document-property.ts +41 -23
- package/corpus/templates/content/actions/share-local-file-document.ts +15 -0
- package/corpus/templates/content/actions/submit-content-database-form.ts +7 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +14 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +1 -0
- package/corpus/templates/content/actions/update-content-sidebar-state.ts +27 -0
- package/corpus/templates/content/actions/update-document.ts +158 -66
- package/corpus/templates/content/app/components/EmptyState.tsx +4 -57
- package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +8 -1
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +519 -178
- package/corpus/templates/content/app/components/editor/DocumentInfoPanel.tsx +39 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +1 -1
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +266 -7
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +430 -152
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +467 -66
- package/corpus/templates/content/app/components/editor/database/view-config.ts +9 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +720 -196
- package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +173 -1
- package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +1 -1
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +91 -16
- package/corpus/templates/content/app/entry.client.tsx +5 -1
- package/corpus/templates/content/app/hooks/use-content-database.ts +84 -3
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +60 -2
- package/corpus/templates/content/app/hooks/use-create-page.ts +30 -0
- package/corpus/templates/content/app/hooks/use-documents.ts +270 -19
- package/corpus/templates/content/app/i18n-data.ts +243 -20
- package/corpus/templates/content/app/lib/local-folder-picker-safety.ts +54 -0
- package/corpus/templates/content/app/lib/local-folder-picker-support.ts +18 -0
- package/corpus/templates/content/app/routes/_app.favorites.tsx +30 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +18 -6
- package/corpus/templates/content/changelog/2026-07-17-workspace-files-now-stay-in-a-loading-state-until-automatic-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspace-switching-now-opens-the-selected-files-database-pr.md +6 -0
- package/corpus/templates/content/changelog/2026-07-17-workspaces-now-expand-above-their-own-files-list-without-dup.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-create-named-workspaces-each-with-its-own-private-files-data.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-favorites-align-with-workspace-and-file-rows-in-the-sidebar.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-local-folder-selection-no-longer-invokes-the-unsafe-native-p.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-made-content-workspaces-independently-expandable-restored-or.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-page-details-now-live-in-a-shared-info-and-comments-rail-dat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-database-views-now-control-sidebar-workspace-navig.md +6 -0
- package/corpus/templates/content/changelog/2026-07-18-workspace-navigation-now-aligns-favorites-with-workspace-pag.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-favorites-now-open-as-a-table-and-compact-breadcrumbs-make-w.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-files-databases-now-start-unfiltered-sidebar-rows-exactly-fo.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-sidebar-page-trees-now-open-only-when-expanded-remember-thei.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-file-databases-now-use-the-workspace-name-open-as-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-navigation-now-stays-stable-on-hover-uses-folder-i.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspace-rows-now-use-folder-icons-and-deleting-a-user-crea.md +6 -0
- package/corpus/templates/content/changelog/2026-07-19-workspaces-can-be-created-without-leaving-the-workspaces-dat.md +6 -0
- package/corpus/templates/content/e2e/global-setup.ts +3 -2
- package/corpus/templates/content/parity/matrix.md +28 -28
- package/corpus/templates/content/parity/matrix.ts +8 -2
- package/corpus/templates/content/server/db/index.ts +1 -0
- package/corpus/templates/content/server/db/schema.ts +10 -0
- package/corpus/templates/content/server/lib/documents.ts +12 -7
- package/corpus/templates/content/server/plugins/db.ts +17 -0
- package/corpus/templates/content/shared/api.ts +9 -0
- package/corpus/templates/content/shared/database-form.ts +11 -2
- package/corpus/templates/content/vite.config.ts +1 -0
- package/dist/client/build-compatibility.d.ts +11 -0
- package/dist/client/build-compatibility.d.ts.map +1 -0
- package/dist/client/build-compatibility.js +56 -0
- package/dist/client/build-compatibility.js.map +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts +1 -0
- package/dist/client/route-chunk-recovery/index.d.ts.map +1 -1
- package/dist/client/route-chunk-recovery/index.js +1 -0
- package/dist/client/route-chunk-recovery/index.js.map +1 -1
- package/dist/client/use-action.d.ts.map +1 -1
- package/dist/client/use-action.js +19 -1
- package/dist/client/use-action.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/deploy/build.js +2 -1
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/context.d.ts +4 -2
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +44 -22
- package/dist/org/context.js.map +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +12 -12
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +32 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/auth-plugin.d.ts.map +1 -1
- package/dist/server/auth-plugin.js +9 -5
- package/dist/server/auth-plugin.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +4 -1
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts +1 -1
- package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
- package/dist/shared/mcp-embed-headers.js +1 -1
- package/dist/shared/mcp-embed-headers.js.map +1 -1
- package/dist/vite/client.d.ts +6 -0
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +8 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/build-compatibility.ts +77 -0
- package/src/client/route-chunk-recovery/index.ts +1 -0
- package/src/client/use-action.ts +32 -1
- package/src/deploy/build.ts +4 -1
- package/src/org/context.ts +56 -30
- package/src/server/action-routes.ts +52 -2
- package/src/server/auth-plugin.ts +9 -4
- package/src/server/better-auth-instance.ts +5 -1
- package/src/shared/mcp-embed-headers.ts +1 -1
- package/src/vite/client.ts +17 -0
|
@@ -1,14 +1,24 @@
|
|
|
1
1
|
import { createHash } from "node:crypto";
|
|
2
2
|
|
|
3
|
-
import { and, eq, sql } from "drizzle-orm";
|
|
3
|
+
import { and, eq, inArray, sql } from "drizzle-orm";
|
|
4
4
|
|
|
5
5
|
import { schema } from "../server/db/index.js";
|
|
6
|
+
import {
|
|
7
|
+
isComputedPropertyType,
|
|
8
|
+
type DocumentPropertyType,
|
|
9
|
+
} from "../shared/properties.js";
|
|
6
10
|
import {
|
|
7
11
|
listContentOrganizationMemberships,
|
|
8
12
|
normalizeContentSpaceEmail,
|
|
9
13
|
} from "./_content-space-access.js";
|
|
14
|
+
import {
|
|
15
|
+
defaultFilesDatabaseViewConfig,
|
|
16
|
+
ensureFilesSystemPropertyDefinitions,
|
|
17
|
+
} from "./_files-system-properties.js";
|
|
18
|
+
import { withPositionLock } from "./_position-utils.js";
|
|
10
19
|
import {
|
|
11
20
|
defaultDatabaseViewConfig,
|
|
21
|
+
normalizedValueJson,
|
|
12
22
|
seedDefaultBlocksField,
|
|
13
23
|
serializeDatabaseViewConfig,
|
|
14
24
|
} from "./_property-utils.js";
|
|
@@ -19,6 +29,8 @@ export type ProvisionedContentSpaces = {
|
|
|
19
29
|
personalSpaceId: string;
|
|
20
30
|
personalFilesDatabaseId: string;
|
|
21
31
|
catalogDatabaseId: string;
|
|
32
|
+
favoritesDatabaseId: string;
|
|
33
|
+
favoritesDocumentId: string;
|
|
22
34
|
spaceIds: string[];
|
|
23
35
|
created: {
|
|
24
36
|
spaces: number;
|
|
@@ -50,9 +62,16 @@ export function sourceBackedContentSpaceId(
|
|
|
50
62
|
);
|
|
51
63
|
}
|
|
52
64
|
|
|
65
|
+
export function userContentSpaceId(email: string, workspaceId: string) {
|
|
66
|
+
return opaqueId(
|
|
67
|
+
"content_space_user",
|
|
68
|
+
`${normalizeContentSpaceEmail(email)}:${workspaceId.trim()}`,
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
53
72
|
export function systemIdsForContentSpace(
|
|
54
73
|
scope: string,
|
|
55
|
-
role: "files" | "workspaces",
|
|
74
|
+
role: "files" | "workspaces" | "favorites",
|
|
56
75
|
) {
|
|
57
76
|
return {
|
|
58
77
|
databaseId: opaqueId(`content_database_${role}`, scope),
|
|
@@ -90,7 +109,7 @@ async function ensureSystemDatabase(args: {
|
|
|
90
109
|
ownerEmail: string;
|
|
91
110
|
orgId: string | null;
|
|
92
111
|
title: string;
|
|
93
|
-
role: "files" | "workspaces";
|
|
112
|
+
role: "files" | "workspaces" | "favorites";
|
|
94
113
|
visibility: "private" | "org";
|
|
95
114
|
now: string;
|
|
96
115
|
created: ProvisionedContentSpaces["created"];
|
|
@@ -137,7 +156,9 @@ async function ensureSystemDatabase(args: {
|
|
|
137
156
|
title: args.title,
|
|
138
157
|
systemRole: args.role,
|
|
139
158
|
viewConfigJson: serializeDatabaseViewConfig(
|
|
140
|
-
|
|
159
|
+
args.role === "files"
|
|
160
|
+
? defaultFilesDatabaseViewConfig(ids.databaseId)
|
|
161
|
+
: defaultDatabaseViewConfig("table"),
|
|
141
162
|
),
|
|
142
163
|
createdAt: args.now,
|
|
143
164
|
updatedAt: args.now,
|
|
@@ -158,6 +179,22 @@ async function ensureSystemDatabase(args: {
|
|
|
158
179
|
throw new Error(
|
|
159
180
|
`Unable to provision ${args.role} database for Content space`,
|
|
160
181
|
);
|
|
182
|
+
if (args.role === "files" && database.title === "Files") {
|
|
183
|
+
await args.db
|
|
184
|
+
.update(schema.contentDatabases)
|
|
185
|
+
.set({ title: args.title, updatedAt: args.now })
|
|
186
|
+
.where(eq(schema.contentDatabases.id, database.id));
|
|
187
|
+
await args.db
|
|
188
|
+
.update(schema.documents)
|
|
189
|
+
.set({ title: args.title, updatedAt: args.now })
|
|
190
|
+
.where(eq(schema.documents.id, database.documentId));
|
|
191
|
+
database.title = args.title;
|
|
192
|
+
}
|
|
193
|
+
await ensureFilesSystemPropertyDefinitions({
|
|
194
|
+
database,
|
|
195
|
+
db: args.db,
|
|
196
|
+
now: args.now,
|
|
197
|
+
});
|
|
161
198
|
return database;
|
|
162
199
|
}
|
|
163
200
|
|
|
@@ -214,26 +251,30 @@ export async function provisionContentSpaces(
|
|
|
214
251
|
.databaseId,
|
|
215
252
|
catalogDatabaseId: systemIdsForContentSpace(personalSpaceId, "workspaces")
|
|
216
253
|
.databaseId,
|
|
254
|
+
favoritesDatabaseId: systemIdsForContentSpace(personalSpaceId, "favorites")
|
|
255
|
+
.databaseId,
|
|
256
|
+
favoritesDocumentId: systemIdsForContentSpace(personalSpaceId, "favorites")
|
|
257
|
+
.documentId,
|
|
217
258
|
spaceIds: [],
|
|
218
259
|
created: { spaces: 0, databases: 0, documents: 0, catalogItems: 0 },
|
|
219
260
|
};
|
|
220
261
|
|
|
221
262
|
await db.transaction(async (tx: Db) => {
|
|
263
|
+
const [personalSpace] = await tx
|
|
264
|
+
.select()
|
|
265
|
+
.from(schema.contentSpaces)
|
|
266
|
+
.where(eq(schema.contentSpaces.id, personalSpaceId));
|
|
222
267
|
const personalFiles = await ensureSystemDatabase({
|
|
223
268
|
db: tx,
|
|
224
269
|
spaceId: personalSpaceId,
|
|
225
270
|
ownerEmail: email,
|
|
226
271
|
orgId: null,
|
|
227
|
-
title: "
|
|
272
|
+
title: personalSpace?.name ?? "Personal",
|
|
228
273
|
role: "files",
|
|
229
274
|
visibility: "private",
|
|
230
275
|
now,
|
|
231
276
|
created: result.created,
|
|
232
277
|
});
|
|
233
|
-
const [personalSpace] = await tx
|
|
234
|
-
.select()
|
|
235
|
-
.from(schema.contentSpaces)
|
|
236
|
-
.where(eq(schema.contentSpaces.id, personalSpaceId));
|
|
237
278
|
if (!personalSpace) {
|
|
238
279
|
await tx
|
|
239
280
|
.insert(schema.contentSpaces)
|
|
@@ -267,20 +308,31 @@ export async function provisionContentSpaces(
|
|
|
267
308
|
now,
|
|
268
309
|
created: result.created,
|
|
269
310
|
});
|
|
270
|
-
await
|
|
311
|
+
const [existingFavorites] = await tx
|
|
312
|
+
.select({ id: schema.contentDatabases.id })
|
|
313
|
+
.from(schema.contentDatabases)
|
|
314
|
+
.where(
|
|
315
|
+
and(
|
|
316
|
+
eq(schema.contentDatabases.spaceId, personalSpaceId),
|
|
317
|
+
eq(schema.contentDatabases.systemRole, "favorites"),
|
|
318
|
+
),
|
|
319
|
+
);
|
|
320
|
+
const favorites = await ensureSystemDatabase({
|
|
271
321
|
db: tx,
|
|
272
|
-
|
|
273
|
-
documentId: catalog.documentId,
|
|
322
|
+
spaceId: personalSpaceId,
|
|
274
323
|
ownerEmail: email,
|
|
275
324
|
orgId: null,
|
|
276
|
-
|
|
325
|
+
title: "Favorites",
|
|
326
|
+
role: "favorites",
|
|
327
|
+
visibility: "private",
|
|
277
328
|
now,
|
|
329
|
+
created: result.created,
|
|
278
330
|
});
|
|
279
331
|
|
|
280
332
|
const spaces = [
|
|
281
333
|
{
|
|
282
334
|
id: personalSpaceId,
|
|
283
|
-
name: "Personal",
|
|
335
|
+
name: personalSpace?.name ?? "Personal",
|
|
284
336
|
ownerEmail: email,
|
|
285
337
|
orgId: null as string | null,
|
|
286
338
|
createdBy: email,
|
|
@@ -298,31 +350,23 @@ export async function provisionContentSpaces(
|
|
|
298
350
|
).databaseId,
|
|
299
351
|
})),
|
|
300
352
|
];
|
|
301
|
-
const provisionableOrganizationSpaceIds = new Set(
|
|
302
|
-
memberships
|
|
303
|
-
.filter(
|
|
304
|
-
(membership) =>
|
|
305
|
-
membership.role === "owner" || membership.role === "admin",
|
|
306
|
-
)
|
|
307
|
-
.map((membership) => organizationContentSpaceId(membership.orgId)),
|
|
308
|
-
);
|
|
309
353
|
for (const space of spaces.slice(1)) {
|
|
310
|
-
|
|
354
|
+
const [existingSpace] = await tx
|
|
355
|
+
.select()
|
|
356
|
+
.from(schema.contentSpaces)
|
|
357
|
+
.where(eq(schema.contentSpaces.id, space.id));
|
|
358
|
+
if (existingSpace) space.name = existingSpace.name;
|
|
311
359
|
const files = await ensureSystemDatabase({
|
|
312
360
|
db: tx,
|
|
313
361
|
spaceId: space.id,
|
|
314
362
|
ownerEmail: space.ownerEmail,
|
|
315
363
|
orgId: space.orgId,
|
|
316
|
-
title:
|
|
364
|
+
title: space.name,
|
|
317
365
|
role: "files",
|
|
318
366
|
visibility: "org",
|
|
319
367
|
now,
|
|
320
368
|
created: result.created,
|
|
321
369
|
});
|
|
322
|
-
const [existingSpace] = await tx
|
|
323
|
-
.select()
|
|
324
|
-
.from(schema.contentSpaces)
|
|
325
|
-
.where(eq(schema.contentSpaces.id, space.id));
|
|
326
370
|
if (!existingSpace) {
|
|
327
371
|
await tx
|
|
328
372
|
.insert(schema.contentSpaces)
|
|
@@ -335,20 +379,65 @@ export async function provisionContentSpaces(
|
|
|
335
379
|
})
|
|
336
380
|
.onConflictDoNothing();
|
|
337
381
|
result.created.spaces += 1;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
existingSpace.filesDatabaseId !== files.id
|
|
341
|
-
) {
|
|
382
|
+
}
|
|
383
|
+
if (existingSpace && existingSpace.filesDatabaseId !== files.id) {
|
|
342
384
|
await tx
|
|
343
385
|
.update(schema.contentSpaces)
|
|
344
386
|
.set({
|
|
345
|
-
name: space.name,
|
|
346
387
|
filesDatabaseId: files.id,
|
|
347
388
|
updatedAt: now,
|
|
348
389
|
})
|
|
349
390
|
.where(eq(schema.contentSpaces.id, space.id));
|
|
350
391
|
}
|
|
351
392
|
}
|
|
393
|
+
if (!existingFavorites) {
|
|
394
|
+
const accessibleSpaceIds = spaces.map((space) => space.id);
|
|
395
|
+
const legacyFavorites: Array<typeof schema.documents.$inferSelect> =
|
|
396
|
+
await tx
|
|
397
|
+
.select()
|
|
398
|
+
.from(schema.documents)
|
|
399
|
+
.where(
|
|
400
|
+
and(
|
|
401
|
+
eq(schema.documents.isFavorite, 1),
|
|
402
|
+
inArray(schema.documents.spaceId, accessibleSpaceIds),
|
|
403
|
+
),
|
|
404
|
+
);
|
|
405
|
+
const roleByOrgId = new Map(
|
|
406
|
+
memberships.map((membership) => [membership.orgId, membership.role]),
|
|
407
|
+
);
|
|
408
|
+
const visibleLegacyFavorites = legacyFavorites.filter((document) => {
|
|
409
|
+
if (!document.orgId) {
|
|
410
|
+
return normalizeContentSpaceEmail(document.ownerEmail) === email;
|
|
411
|
+
}
|
|
412
|
+
return (
|
|
413
|
+
normalizeContentSpaceEmail(document.ownerEmail) === email ||
|
|
414
|
+
document.visibility === "org" ||
|
|
415
|
+
document.visibility === "public" ||
|
|
416
|
+
roleByOrgId.get(document.orgId) === "owner" ||
|
|
417
|
+
roleByOrgId.get(document.orgId) === "admin"
|
|
418
|
+
);
|
|
419
|
+
});
|
|
420
|
+
if (visibleLegacyFavorites.length > 0) {
|
|
421
|
+
await tx
|
|
422
|
+
.insert(schema.contentDatabaseItems)
|
|
423
|
+
.values(
|
|
424
|
+
visibleLegacyFavorites.map((document, position) => ({
|
|
425
|
+
id: opaqueId(
|
|
426
|
+
"content_database_item",
|
|
427
|
+
`${favorites.id}:${document.id}`,
|
|
428
|
+
),
|
|
429
|
+
ownerEmail: email,
|
|
430
|
+
orgId: null,
|
|
431
|
+
databaseId: favorites.id,
|
|
432
|
+
documentId: document.id,
|
|
433
|
+
position,
|
|
434
|
+
createdAt: now,
|
|
435
|
+
updatedAt: now,
|
|
436
|
+
})),
|
|
437
|
+
)
|
|
438
|
+
.onConflictDoNothing();
|
|
439
|
+
}
|
|
440
|
+
}
|
|
352
441
|
const accessibleIds = new Set(spaces.map((space) => space.id));
|
|
353
442
|
for (const [index, space] of spaces.entries()) {
|
|
354
443
|
if (!accessibleIds.has(space.id)) continue;
|
|
@@ -395,15 +484,6 @@ export async function provisionContentSpaces(
|
|
|
395
484
|
position: index,
|
|
396
485
|
now,
|
|
397
486
|
});
|
|
398
|
-
await ensureDatabaseItem({
|
|
399
|
-
db: tx,
|
|
400
|
-
databaseId: personalFiles.id,
|
|
401
|
-
documentId: referenceDocumentId,
|
|
402
|
-
ownerEmail: email,
|
|
403
|
-
orgId: null,
|
|
404
|
-
position: index + 1,
|
|
405
|
-
now,
|
|
406
|
-
});
|
|
407
487
|
const [existingCatalogItem] = await tx
|
|
408
488
|
.select({ id: schema.contentSpaceCatalogItems.id })
|
|
409
489
|
.from(schema.contentSpaceCatalogItems)
|
|
@@ -451,7 +531,6 @@ export async function provisionContentSpaces(
|
|
|
451
531
|
db,
|
|
452
532
|
});
|
|
453
533
|
for (const membership of memberships) {
|
|
454
|
-
if (membership.role !== "owner" && membership.role !== "admin") continue;
|
|
455
534
|
const spaceId = organizationContentSpaceId(membership.orgId);
|
|
456
535
|
const [database] = await db
|
|
457
536
|
.select({
|
|
@@ -484,21 +563,24 @@ export async function provisionContentSpaces(
|
|
|
484
563
|
return result;
|
|
485
564
|
}
|
|
486
565
|
|
|
487
|
-
|
|
566
|
+
async function provisionOwnedContentSpace(
|
|
488
567
|
db: Db,
|
|
489
568
|
userEmail: string,
|
|
490
|
-
input: {
|
|
569
|
+
input: {
|
|
570
|
+
spaceId: string;
|
|
571
|
+
name: string;
|
|
572
|
+
kind: "user" | "source_backed";
|
|
573
|
+
propertyValues?: Record<string, unknown>;
|
|
574
|
+
},
|
|
491
575
|
) {
|
|
492
576
|
const email = normalizeContentSpaceEmail(userEmail);
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
if (!connectionId) throw new Error("Local folder connection ID is required");
|
|
577
|
+
const name = input.name.trim();
|
|
578
|
+
if (!name) throw new Error("Workspace name is required");
|
|
496
579
|
await provisionContentSpaces(db, email);
|
|
497
580
|
|
|
498
581
|
const now = new Date().toISOString();
|
|
499
|
-
const spaceId =
|
|
582
|
+
const spaceId = input.spaceId;
|
|
500
583
|
const personalSpaceId = personalContentSpaceId(email);
|
|
501
|
-
const personalFilesIds = systemIdsForContentSpace(personalSpaceId, "files");
|
|
502
584
|
const catalogIds = systemIdsForContentSpace(personalSpaceId, "workspaces");
|
|
503
585
|
const created: ProvisionedContentSpaces["created"] = {
|
|
504
586
|
spaces: 0,
|
|
@@ -507,134 +589,249 @@ export async function provisionSourceBackedContentSpace(
|
|
|
507
589
|
catalogItems: 0,
|
|
508
590
|
};
|
|
509
591
|
|
|
510
|
-
const
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
});
|
|
522
|
-
const [existingSpace] = await tx
|
|
523
|
-
.select()
|
|
524
|
-
.from(schema.contentSpaces)
|
|
525
|
-
.where(eq(schema.contentSpaces.id, spaceId));
|
|
526
|
-
if (!existingSpace) {
|
|
527
|
-
await tx
|
|
528
|
-
.insert(schema.contentSpaces)
|
|
529
|
-
.values({
|
|
530
|
-
id: spaceId,
|
|
531
|
-
name,
|
|
532
|
-
kind: "source_backed",
|
|
533
|
-
ownerEmail: email,
|
|
534
|
-
orgId: null,
|
|
535
|
-
filesDatabaseId: sourceFiles.id,
|
|
536
|
-
createdBy: email,
|
|
537
|
-
createdAt: now,
|
|
538
|
-
updatedAt: now,
|
|
539
|
-
})
|
|
540
|
-
.onConflictDoNothing();
|
|
541
|
-
} else if (
|
|
542
|
-
existingSpace.name !== name ||
|
|
543
|
-
existingSpace.filesDatabaseId !== sourceFiles.id
|
|
544
|
-
) {
|
|
545
|
-
await tx
|
|
546
|
-
.update(schema.contentSpaces)
|
|
547
|
-
.set({ name, filesDatabaseId: sourceFiles.id, updatedAt: now })
|
|
592
|
+
const provisioned = await withPositionLock<{
|
|
593
|
+
files: typeof schema.contentDatabases.$inferSelect;
|
|
594
|
+
name: string;
|
|
595
|
+
catalogDatabaseId: string;
|
|
596
|
+
catalogItemId: string;
|
|
597
|
+
catalogDocumentId: string;
|
|
598
|
+
}>(`contentSpace:${spaceId}`, () =>
|
|
599
|
+
db.transaction(async (tx: Db) => {
|
|
600
|
+
const [existingSpace] = await tx
|
|
601
|
+
.select()
|
|
602
|
+
.from(schema.contentSpaces)
|
|
548
603
|
.where(eq(schema.contentSpaces.id, spaceId));
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
"content_workspace_reference",
|
|
553
|
-
`${email}:${spaceId}`,
|
|
554
|
-
);
|
|
555
|
-
await ensureDocument(
|
|
556
|
-
tx,
|
|
557
|
-
{
|
|
558
|
-
id: referenceDocumentId,
|
|
559
|
-
spaceId: personalSpaceId,
|
|
604
|
+
const sourceFiles = await ensureSystemDatabase({
|
|
605
|
+
db: tx,
|
|
606
|
+
spaceId,
|
|
560
607
|
ownerEmail: email,
|
|
561
608
|
orgId: null,
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
content: "",
|
|
565
|
-
description: "",
|
|
566
|
-
position: 0,
|
|
567
|
-
isFavorite: 0,
|
|
568
|
-
hideFromSearch: 0,
|
|
609
|
+
title: existingSpace?.name ?? name,
|
|
610
|
+
role: "files",
|
|
569
611
|
visibility: "private",
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
}
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
612
|
+
now,
|
|
613
|
+
created,
|
|
614
|
+
});
|
|
615
|
+
if (
|
|
616
|
+
existingSpace &&
|
|
617
|
+
input.kind === "user" &&
|
|
618
|
+
existingSpace.name !== name
|
|
619
|
+
) {
|
|
620
|
+
throw new Error(
|
|
621
|
+
"Workspace request ID is already bound to another name",
|
|
622
|
+
);
|
|
623
|
+
}
|
|
624
|
+
let createdSpace = false;
|
|
625
|
+
if (!existingSpace) {
|
|
626
|
+
const inserted = await tx
|
|
627
|
+
.insert(schema.contentSpaces)
|
|
628
|
+
.values({
|
|
629
|
+
id: spaceId,
|
|
630
|
+
name,
|
|
631
|
+
kind: input.kind,
|
|
632
|
+
ownerEmail: email,
|
|
633
|
+
orgId: null,
|
|
634
|
+
filesDatabaseId: sourceFiles.id,
|
|
635
|
+
createdBy: email,
|
|
636
|
+
createdAt: now,
|
|
637
|
+
updatedAt: now,
|
|
638
|
+
})
|
|
639
|
+
.onConflictDoNothing()
|
|
640
|
+
.returning({ id: schema.contentSpaces.id });
|
|
641
|
+
createdSpace = inserted.length > 0;
|
|
642
|
+
} else if (existingSpace.filesDatabaseId !== sourceFiles.id) {
|
|
643
|
+
await tx
|
|
644
|
+
.update(schema.contentSpaces)
|
|
645
|
+
.set({ filesDatabaseId: sourceFiles.id, updatedAt: now })
|
|
646
|
+
.where(eq(schema.contentSpaces.id, spaceId));
|
|
647
|
+
}
|
|
648
|
+
const [canonicalSpace] = await tx
|
|
649
|
+
.select()
|
|
650
|
+
.from(schema.contentSpaces)
|
|
651
|
+
.where(eq(schema.contentSpaces.id, spaceId));
|
|
652
|
+
if (!canonicalSpace)
|
|
653
|
+
throw new Error("Unable to create Content workspace");
|
|
654
|
+
if (input.kind === "user" && canonicalSpace.name !== name) {
|
|
655
|
+
throw new Error(
|
|
656
|
+
"Workspace request ID is already bound to another name",
|
|
657
|
+
);
|
|
658
|
+
}
|
|
659
|
+
const referenceDocumentId = opaqueId(
|
|
660
|
+
"content_workspace_reference",
|
|
661
|
+
`${email}:${spaceId}`,
|
|
613
662
|
);
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
663
|
+
await ensureDocument(
|
|
664
|
+
tx,
|
|
665
|
+
{
|
|
666
|
+
id: referenceDocumentId,
|
|
667
|
+
spaceId: personalSpaceId,
|
|
619
668
|
ownerEmail: email,
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
669
|
+
orgId: null,
|
|
670
|
+
parentId: catalogIds.documentId,
|
|
671
|
+
title: canonicalSpace.name,
|
|
672
|
+
content: "",
|
|
673
|
+
description: "",
|
|
674
|
+
position: 0,
|
|
675
|
+
isFavorite: 0,
|
|
676
|
+
hideFromSearch: 0,
|
|
677
|
+
visibility: "private",
|
|
624
678
|
createdAt: now,
|
|
625
679
|
updatedAt: now,
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
680
|
+
},
|
|
681
|
+
created,
|
|
682
|
+
);
|
|
683
|
+
if (createdSpace) {
|
|
684
|
+
await tx
|
|
685
|
+
.update(schema.documents)
|
|
686
|
+
.set({ title: canonicalSpace.name, updatedAt: now })
|
|
687
|
+
.where(eq(schema.documents.id, referenceDocumentId));
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
const [maxCatalogPosition] = await tx
|
|
691
|
+
.select({ max: sql<number>`COALESCE(MAX(position), -1)` })
|
|
692
|
+
.from(schema.contentDatabaseItems)
|
|
693
|
+
.where(
|
|
694
|
+
eq(schema.contentDatabaseItems.databaseId, catalogIds.databaseId),
|
|
695
|
+
);
|
|
696
|
+
const catalogItemId = await ensureDatabaseItem({
|
|
697
|
+
db: tx,
|
|
698
|
+
databaseId: catalogIds.databaseId,
|
|
699
|
+
documentId: referenceDocumentId,
|
|
700
|
+
ownerEmail: email,
|
|
701
|
+
orgId: null,
|
|
702
|
+
position: (maxCatalogPosition?.max ?? -1) + 1,
|
|
703
|
+
now,
|
|
704
|
+
});
|
|
705
|
+
const [mapping] = await tx
|
|
706
|
+
.select({ id: schema.contentSpaceCatalogItems.id })
|
|
707
|
+
.from(schema.contentSpaceCatalogItems)
|
|
708
|
+
.where(
|
|
709
|
+
and(
|
|
710
|
+
eq(
|
|
711
|
+
schema.contentSpaceCatalogItems.catalogDatabaseId,
|
|
712
|
+
catalogIds.databaseId,
|
|
713
|
+
),
|
|
714
|
+
eq(schema.contentSpaceCatalogItems.spaceId, spaceId),
|
|
715
|
+
),
|
|
716
|
+
);
|
|
717
|
+
if (!mapping) {
|
|
718
|
+
await tx
|
|
719
|
+
.insert(schema.contentSpaceCatalogItems)
|
|
720
|
+
.values({
|
|
721
|
+
id: opaqueId("content_space_catalog", `${email}:${spaceId}`),
|
|
722
|
+
ownerEmail: email,
|
|
723
|
+
catalogDatabaseId: catalogIds.databaseId,
|
|
724
|
+
databaseItemId: catalogItemId,
|
|
725
|
+
documentId: referenceDocumentId,
|
|
726
|
+
spaceId,
|
|
727
|
+
createdAt: now,
|
|
728
|
+
updatedAt: now,
|
|
729
|
+
})
|
|
730
|
+
.onConflictDoNothing();
|
|
731
|
+
}
|
|
732
|
+
const initialPropertyValues = Object.entries(input.propertyValues ?? {});
|
|
733
|
+
if (createdSpace && initialPropertyValues.length > 0) {
|
|
734
|
+
const definitions = (await tx
|
|
735
|
+
.select()
|
|
736
|
+
.from(schema.documentPropertyDefinitions)
|
|
737
|
+
.where(
|
|
738
|
+
and(
|
|
739
|
+
eq(
|
|
740
|
+
schema.documentPropertyDefinitions.databaseId,
|
|
741
|
+
catalogIds.databaseId,
|
|
742
|
+
),
|
|
743
|
+
inArray(
|
|
744
|
+
schema.documentPropertyDefinitions.id,
|
|
745
|
+
initialPropertyValues.map(([propertyId]) => propertyId),
|
|
746
|
+
),
|
|
747
|
+
),
|
|
748
|
+
)) as Array<typeof schema.documentPropertyDefinitions.$inferSelect>;
|
|
749
|
+
const definitionById = new Map(
|
|
750
|
+
definitions.map((definition) => [definition.id, definition]),
|
|
751
|
+
);
|
|
752
|
+
for (const [propertyId, value] of initialPropertyValues) {
|
|
753
|
+
const definition = definitionById.get(propertyId);
|
|
754
|
+
const type = definition?.type as DocumentPropertyType | undefined;
|
|
755
|
+
if (!type || isComputedPropertyType(type)) continue;
|
|
756
|
+
await tx
|
|
757
|
+
.insert(schema.documentPropertyValues)
|
|
758
|
+
.values({
|
|
759
|
+
id: opaqueId(
|
|
760
|
+
"content_workspace_property",
|
|
761
|
+
`${email}:${spaceId}:${propertyId}`,
|
|
762
|
+
),
|
|
763
|
+
ownerEmail: email,
|
|
764
|
+
documentId: referenceDocumentId,
|
|
765
|
+
propertyId,
|
|
766
|
+
valueJson: normalizedValueJson(type, value),
|
|
767
|
+
createdAt: now,
|
|
768
|
+
updatedAt: now,
|
|
769
|
+
})
|
|
770
|
+
.onConflictDoUpdate({
|
|
771
|
+
target: schema.documentPropertyValues.id,
|
|
772
|
+
set: {
|
|
773
|
+
valueJson: normalizedValueJson(type, value),
|
|
774
|
+
updatedAt: now,
|
|
775
|
+
},
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
return {
|
|
780
|
+
files: sourceFiles,
|
|
781
|
+
name: canonicalSpace.name,
|
|
782
|
+
catalogDatabaseId: catalogIds.databaseId,
|
|
783
|
+
catalogItemId,
|
|
784
|
+
catalogDocumentId: referenceDocumentId,
|
|
785
|
+
};
|
|
786
|
+
}),
|
|
787
|
+
);
|
|
631
788
|
|
|
632
789
|
await seedDefaultBlocksField({
|
|
633
|
-
databaseId: files.id,
|
|
634
|
-
ownerEmail: files.ownerEmail,
|
|
635
|
-
orgId: files.orgId,
|
|
790
|
+
databaseId: provisioned.files.id,
|
|
791
|
+
ownerEmail: provisioned.files.ownerEmail,
|
|
792
|
+
orgId: provisioned.files.orgId,
|
|
636
793
|
now,
|
|
637
794
|
db,
|
|
638
795
|
});
|
|
639
|
-
return {
|
|
796
|
+
return {
|
|
797
|
+
spaceId,
|
|
798
|
+
name: provisioned.name,
|
|
799
|
+
filesDatabaseId: provisioned.files.id,
|
|
800
|
+
catalogDatabaseId: provisioned.catalogDatabaseId,
|
|
801
|
+
catalogItemId: provisioned.catalogItemId,
|
|
802
|
+
catalogDocumentId: provisioned.catalogDocumentId,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export async function provisionSourceBackedContentSpace(
|
|
807
|
+
db: Db,
|
|
808
|
+
userEmail: string,
|
|
809
|
+
input: { connectionId: string; name: string },
|
|
810
|
+
) {
|
|
811
|
+
const connectionId = input.connectionId.trim();
|
|
812
|
+
if (!connectionId) throw new Error("Local folder connection ID is required");
|
|
813
|
+
return provisionOwnedContentSpace(db, userEmail, {
|
|
814
|
+
spaceId: sourceBackedContentSpaceId(userEmail, connectionId),
|
|
815
|
+
name: input.name.trim() || "Local folder",
|
|
816
|
+
kind: "source_backed",
|
|
817
|
+
});
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
export async function provisionUserContentSpace(
|
|
821
|
+
db: Db,
|
|
822
|
+
userEmail: string,
|
|
823
|
+
input: {
|
|
824
|
+
workspaceId: string;
|
|
825
|
+
name: string;
|
|
826
|
+
propertyValues?: Record<string, unknown>;
|
|
827
|
+
},
|
|
828
|
+
) {
|
|
829
|
+
const workspaceId = input.workspaceId.trim();
|
|
830
|
+
if (!workspaceId) throw new Error("Workspace ID is required");
|
|
831
|
+
return provisionOwnedContentSpace(db, userEmail, {
|
|
832
|
+
spaceId: userContentSpaceId(userEmail, workspaceId),
|
|
833
|
+
name: input.name,
|
|
834
|
+
kind: "user",
|
|
835
|
+
propertyValues: input.propertyValues,
|
|
836
|
+
});
|
|
640
837
|
}
|