@agent-native/core 0.135.0 → 0.135.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.
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/getting-started-actions.mdx +235 -0
- package/corpus/core/docs/content/getting-started-database.mdx +253 -0
- package/corpus/core/docs/content/getting-started-pages.mdx +190 -0
- package/corpus/core/docs/content/getting-started.mdx +57 -613
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/sharing/access.ts +44 -2
- package/corpus/templates/clips/actions/add-comment.ts +6 -2
- package/corpus/templates/clips/app/components/player/comments-panel.tsx +9 -2
- package/corpus/templates/clips/app/components/player/playback-comment-overlay.tsx +44 -27
- package/corpus/templates/clips/app/components/player/scrubber.tsx +10 -2
- package/corpus/templates/clips/app/components/player/video-player.tsx +4 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +22 -15
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +1 -0
- package/corpus/templates/content/actions/_database-utils.ts +113 -4
- package/corpus/templates/content/actions/get-document.ts +84 -3
- package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +9 -2
- package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +29 -13
- package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +5 -12
- package/corpus/templates/content/app/components/editor/body-hydration.ts +12 -6
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2 -3
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -12
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -27
- package/corpus/templates/content/app/hooks/use-create-page.ts +3 -6
- package/corpus/templates/content/app/hooks/use-document-properties.ts +9 -16
- package/corpus/templates/content/app/hooks/use-document-versions.ts +3 -3
- package/corpus/templates/content/app/hooks/use-documents.ts +85 -29
- package/corpus/templates/content/app/hooks/use-notion.ts +15 -13
- package/corpus/templates/content/app/i18n-data.ts +32 -1
- package/corpus/templates/content/app/lib/document-query.ts +36 -0
- package/corpus/templates/content/changelog/2026-08-02-pages-opened-from-a-database-now-keep-that-database-s-fields.md +6 -0
- package/corpus/templates/content/server/lib/document-context.ts +11 -6
- package/corpus/templates/content/shared/api.ts +8 -0
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +6 -6
- package/dist/provider-api/actions/provider-api.d.ts +4 -4
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +32 -2
- package/dist/sharing/access.js.map +1 -1
- package/docs/content/getting-started-actions.mdx +235 -0
- package/docs/content/getting-started-database.mdx +253 -0
- package/docs/content/getting-started-pages.mdx +190 -0
- package/docs/content/getting-started.mdx +57 -613
- package/package.json +1 -1
- package/src/sharing/access.ts +44 -2
|
@@ -16,6 +16,8 @@ import type {
|
|
|
16
16
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
|
17
17
|
import { useEffect, useRef } from "react";
|
|
18
18
|
|
|
19
|
+
import { documentQueryFilter } from "./use-documents";
|
|
20
|
+
|
|
19
21
|
// The server signs a `redirect` query param into the OAuth `state` and the
|
|
20
22
|
// callback route sends the user back there once the connection completes. If
|
|
21
23
|
// we never send it, `state.redirectPath` defaults to "/" server-side and
|
|
@@ -55,9 +57,7 @@ export function invalidateDocumentQueries(
|
|
|
55
57
|
// path after every debounced editor save). Invalidating the bare ["action"]
|
|
56
58
|
// key would refetch every mounted query app-wide (sidebar tree, comments,
|
|
57
59
|
// database views, search, connection status, ...) on each cycle.
|
|
58
|
-
queryClient.invalidateQueries(
|
|
59
|
-
queryKey: ["action", "get-document", { id: documentId }],
|
|
60
|
-
});
|
|
60
|
+
queryClient.invalidateQueries(documentQueryFilter(documentId));
|
|
61
61
|
queryClient.invalidateQueries({
|
|
62
62
|
queryKey: ["action", "list-documents"],
|
|
63
63
|
});
|
|
@@ -155,21 +155,23 @@ export function useDocumentSyncStatus(
|
|
|
155
155
|
|
|
156
156
|
lastObservedSyncedAtRef.current = query.data.lastSyncedAt;
|
|
157
157
|
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
]);
|
|
158
|
+
const cachedDocuments = queryClient
|
|
159
|
+
.getQueriesData<Document>(documentQueryFilter(normalizedDocumentId))
|
|
160
|
+
.map(([, document]) => document)
|
|
161
|
+
.filter((document): document is Document => !!document);
|
|
163
162
|
const syncedLocalUpdatedAt = query.data.lastPushedLocalUpdatedAt;
|
|
164
163
|
|
|
165
164
|
if (
|
|
166
|
-
|
|
165
|
+
cachedDocuments.some(
|
|
166
|
+
(cachedDocument) =>
|
|
167
|
+
!!cachedDocument.updatedAt &&
|
|
168
|
+
!!syncedLocalUpdatedAt &&
|
|
169
|
+
syncedLocalUpdatedAt > cachedDocument.updatedAt,
|
|
170
|
+
) &&
|
|
167
171
|
syncedLocalUpdatedAt &&
|
|
168
|
-
|
|
172
|
+
cachedDocuments.length > 0
|
|
169
173
|
) {
|
|
170
|
-
queryClient.invalidateQueries(
|
|
171
|
-
queryKey: ["action", "get-document", { id: normalizedDocumentId }],
|
|
172
|
-
});
|
|
174
|
+
queryClient.invalidateQueries(documentQueryFilter(normalizedDocumentId));
|
|
173
175
|
queryClient.invalidateQueries({ queryKey: ["action", "list-documents"] });
|
|
174
176
|
}
|
|
175
177
|
}, [
|
|
@@ -3120,9 +3120,12 @@ const enUS = {
|
|
|
3120
3120
|
liveDocumentSaveBeforeSyncFailed:
|
|
3121
3121
|
"The live document could not be saved before syncing.",
|
|
3122
3122
|
documentTitle: "Document title",
|
|
3123
|
-
builderBodySyncing: "
|
|
3123
|
+
builderBodySyncing: "This page's content is still syncing from Builder",
|
|
3124
3124
|
builderBodySyncingDescription:
|
|
3125
3125
|
"Editing is paused until the Builder body finishes syncing, so the existing article content is not overwritten.",
|
|
3126
|
+
pageBodySyncing: "This page's content is still syncing",
|
|
3127
|
+
pageBodySyncingDescription:
|
|
3128
|
+
"Editing is paused until the page body finishes syncing, so existing content is not overwritten.",
|
|
3126
3129
|
localFileSavedHistoryNotUpdated:
|
|
3127
3130
|
"Local file saved, but history was not updated",
|
|
3128
3131
|
reorderField: "Reorder {{name}}",
|
|
@@ -5615,6 +5618,9 @@ const editorMessagesByLocale = {
|
|
|
5615
5618
|
builderBodySyncing: "内容仍在从 Builder 同步",
|
|
5616
5619
|
builderBodySyncingDescription:
|
|
5617
5620
|
"同步 Builder 正文完成前会暂停编辑,避免覆盖现有文章内容。",
|
|
5621
|
+
pageBodySyncing: "此页面的内容仍在同步",
|
|
5622
|
+
pageBodySyncingDescription:
|
|
5623
|
+
"在页面正文完成同步之前,编辑会暂停,以免覆盖现有内容。",
|
|
5618
5624
|
creatingDatabase: "正在创建内联数据库...",
|
|
5619
5625
|
databaseCreated: "内联数据库已创建",
|
|
5620
5626
|
emptyBlockPlaceholder: "按“/”使用命令",
|
|
@@ -5960,6 +5966,9 @@ const editorMessagesByLocale = {
|
|
|
5960
5966
|
builderBodySyncing: "El contenido aún se está sincronizando desde Builder",
|
|
5961
5967
|
builderBodySyncingDescription:
|
|
5962
5968
|
"La edición está en pausa hasta que el cuerpo de Builder termine de sincronizarse, para no sobrescribir el contenido existente del artículo.",
|
|
5969
|
+
pageBodySyncing: "El contenido de esta página aún se está sincronizando",
|
|
5970
|
+
pageBodySyncingDescription:
|
|
5971
|
+
"La edición está en pausa hasta que el contenido de la página termine de sincronizarse, para no sobrescribir el contenido existente.",
|
|
5963
5972
|
creatingDatabase: "Creando base de datos integrada...",
|
|
5964
5973
|
databaseCreated: "Base de datos integrada creada",
|
|
5965
5974
|
emptyBlockPlaceholder: "Pulsa «/» para ver los comandos",
|
|
@@ -6317,6 +6326,10 @@ const editorMessagesByLocale = {
|
|
|
6317
6326
|
"Le contenu est encore en cours de synchronisation depuis Builder",
|
|
6318
6327
|
builderBodySyncingDescription:
|
|
6319
6328
|
"La modification est suspendue jusqu'à la fin de la synchronisation du corps Builder, afin de ne pas écraser le contenu existant de l'article.",
|
|
6329
|
+
pageBodySyncing:
|
|
6330
|
+
"Le contenu de cette page est encore en cours de synchronisation",
|
|
6331
|
+
pageBodySyncingDescription:
|
|
6332
|
+
"La modification est suspendue jusqu'à la fin de la synchronisation du contenu de la page, afin de ne pas écraser le contenu existant.",
|
|
6320
6333
|
creatingDatabase: "Création d'une base de données intégrée...",
|
|
6321
6334
|
databaseCreated: "Base de données intégrée créée",
|
|
6322
6335
|
emptyBlockPlaceholder: "Appuyez sur « / » pour afficher les commandes",
|
|
@@ -6676,6 +6689,9 @@ const editorMessagesByLocale = {
|
|
|
6676
6689
|
builderBodySyncing: "Inhalte werden noch von Builder synchronisiert",
|
|
6677
6690
|
builderBodySyncingDescription:
|
|
6678
6691
|
"Die Bearbeitung ist pausiert, bis der Builder-Textkörper fertig synchronisiert ist, damit der bestehende Artikelinhalt nicht überschrieben wird.",
|
|
6692
|
+
pageBodySyncing: "Der Inhalt dieser Seite wird noch synchronisiert",
|
|
6693
|
+
pageBodySyncingDescription:
|
|
6694
|
+
"Die Bearbeitung ist pausiert, bis der Seiteninhalt fertig synchronisiert ist, damit bestehende Inhalte nicht überschrieben werden.",
|
|
6679
6695
|
creatingDatabase: "Inline-Datenbank wird erstellt...",
|
|
6680
6696
|
databaseCreated: "Inline-Datenbank erstellt",
|
|
6681
6697
|
emptyBlockPlaceholder: "Drücke „/“ für Befehle",
|
|
@@ -7040,6 +7056,9 @@ const editorMessagesByLocale = {
|
|
|
7040
7056
|
builderBodySyncing: "コンテンツはまだ Builder から同期中です",
|
|
7041
7057
|
builderBodySyncingDescription:
|
|
7042
7058
|
"既存の記事内容を上書きしないよう、Builder 本文の同期が完了するまで編集は一時停止されます。",
|
|
7059
|
+
pageBodySyncing: "このページのコンテンツはまだ同期中です",
|
|
7060
|
+
pageBodySyncingDescription:
|
|
7061
|
+
"既存のコンテンツを上書きしないよう、ページ本文の同期が完了するまで編集は一時停止されます。",
|
|
7043
7062
|
creatingDatabase: "インラインデータベースを作成しています...",
|
|
7044
7063
|
databaseCreated: "インラインデータベースが作成されました",
|
|
7045
7064
|
emptyBlockPlaceholder: "「/」でコマンドを表示",
|
|
@@ -7393,6 +7412,9 @@ const editorMessagesByLocale = {
|
|
|
7393
7412
|
builderBodySyncing: "콘텐츠가 아직 Builder에서 동기화되는 중입니다",
|
|
7394
7413
|
builderBodySyncingDescription:
|
|
7395
7414
|
"기존 문서 내용을 덮어쓰지 않도록 Builder 본문 동기화가 완료될 때까지 편집이 일시 중지됩니다.",
|
|
7415
|
+
pageBodySyncing: "이 페이지의 콘텐츠가 아직 동기화 중입니다",
|
|
7416
|
+
pageBodySyncingDescription:
|
|
7417
|
+
"기존 콘텐츠를 덮어쓰지 않도록 페이지 본문 동기화가 완료될 때까지 편집이 일시 중지됩니다.",
|
|
7396
7418
|
creatingDatabase: "인라인 데이터베이스 생성 중...",
|
|
7397
7419
|
databaseCreated: "인라인 데이터베이스가 생성되었습니다.",
|
|
7398
7420
|
emptyBlockPlaceholder: "‘/’를 눌러 명령 사용",
|
|
@@ -7745,6 +7767,9 @@ const editorMessagesByLocale = {
|
|
|
7745
7767
|
builderBodySyncing: "O conteúdo ainda está sincronizando do Builder",
|
|
7746
7768
|
builderBodySyncingDescription:
|
|
7747
7769
|
"A edição fica pausada até o corpo do Builder terminar de sincronizar, para não sobrescrever o conteúdo existente do artigo.",
|
|
7770
|
+
pageBodySyncing: "O conteúdo desta página ainda está sincronizando",
|
|
7771
|
+
pageBodySyncingDescription:
|
|
7772
|
+
"A edição fica pausada até o conteúdo da página terminar de sincronizar, para não sobrescrever o conteúdo existente.",
|
|
7748
7773
|
creatingDatabase: "Criando banco de dados embutido...",
|
|
7749
7774
|
databaseCreated: "Banco de dados embutido criado",
|
|
7750
7775
|
emptyBlockPlaceholder: "Pressione “/” para comandos",
|
|
@@ -8103,6 +8128,9 @@ const editorMessagesByLocale = {
|
|
|
8103
8128
|
builderBodySyncing: "सामग्री अभी भी Builder से सिंक हो रही है",
|
|
8104
8129
|
builderBodySyncingDescription:
|
|
8105
8130
|
"Builder का मुख्य भाग सिंक पूरा होने तक संपादन रोका गया है, ताकि मौजूदा लेख सामग्री अधिलेखित न हो।",
|
|
8131
|
+
pageBodySyncing: "इस पेज की सामग्री अभी भी सिंक हो रही है",
|
|
8132
|
+
pageBodySyncingDescription:
|
|
8133
|
+
"पेज का मुख्य भाग सिंक पूरा होने तक संपादन रोका गया है, ताकि मौजूदा सामग्री अधिलेखित न हो।",
|
|
8106
8134
|
creatingDatabase: "इनलाइन डेटाबेस बनाया जा रहा है...",
|
|
8107
8135
|
databaseCreated: "इनलाइन डेटाबेस बनाया गया",
|
|
8108
8136
|
emptyBlockPlaceholder: "कमांड के लिए '/' दबाएं",
|
|
@@ -8450,6 +8478,9 @@ const editorMessagesByLocale = {
|
|
|
8450
8478
|
builderBodySyncing: "لا يزال المحتوى قيد المزامنة من Builder",
|
|
8451
8479
|
builderBodySyncingDescription:
|
|
8452
8480
|
"يتم إيقاف التحرير مؤقتًا حتى تكتمل مزامنة نص Builder، حتى لا يتم استبدال محتوى المقالة الحالي.",
|
|
8481
|
+
pageBodySyncing: "لا يزال محتوى هذه الصفحة قيد المزامنة",
|
|
8482
|
+
pageBodySyncingDescription:
|
|
8483
|
+
"يتم إيقاف التحرير مؤقتًا حتى تكتمل مزامنة محتوى الصفحة، حتى لا تتم الكتابة فوق المحتوى الحالي.",
|
|
8453
8484
|
creatingDatabase: "جارٍ إنشاء قاعدة بيانات مضمنة...",
|
|
8454
8485
|
databaseCreated: "تم إنشاء قاعدة البيانات المضمنة",
|
|
8455
8486
|
emptyBlockPlaceholder: 'اضغط على "/" للأوامر',
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export interface DocumentQueryContext {
|
|
2
|
+
databaseId?: string | null;
|
|
3
|
+
databaseDocumentId?: string | null;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export function documentQueryKey(
|
|
7
|
+
documentId: string,
|
|
8
|
+
context: DocumentQueryContext = {},
|
|
9
|
+
) {
|
|
10
|
+
return [
|
|
11
|
+
"action",
|
|
12
|
+
"get-document",
|
|
13
|
+
{
|
|
14
|
+
id: documentId,
|
|
15
|
+
...(context.databaseId ? { databaseId: context.databaseId } : {}),
|
|
16
|
+
...(context.databaseDocumentId
|
|
17
|
+
? { databaseDocumentId: context.databaseDocumentId }
|
|
18
|
+
: {}),
|
|
19
|
+
},
|
|
20
|
+
] as const;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function documentQueryFilter(documentId: string) {
|
|
24
|
+
return {
|
|
25
|
+
queryKey: ["action", "get-document"] as const,
|
|
26
|
+
predicate: (query: { queryKey: readonly unknown[] }) => {
|
|
27
|
+
const args = query.queryKey[2];
|
|
28
|
+
return (
|
|
29
|
+
!!args &&
|
|
30
|
+
typeof args === "object" &&
|
|
31
|
+
"id" in args &&
|
|
32
|
+
args.id === documentId
|
|
33
|
+
);
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
}
|
|
@@ -14,6 +14,7 @@ export type DocumentContextPathEntry = {
|
|
|
14
14
|
* assembles the live path without copying ancestor prose into descendants. */
|
|
15
15
|
export async function getDocumentContextPath(
|
|
16
16
|
document: Pick<typeof schema.documents.$inferSelect, "id" | "parentId">,
|
|
17
|
+
options: { databaseId?: string } = {},
|
|
17
18
|
): Promise<DocumentContextPathEntry[]> {
|
|
18
19
|
const db = getDb();
|
|
19
20
|
const path: DocumentContextPathEntry[] = [];
|
|
@@ -43,6 +44,15 @@ export async function getDocumentContextPath(
|
|
|
43
44
|
parentId = parent.parentId;
|
|
44
45
|
}
|
|
45
46
|
|
|
47
|
+
const membershipClauses = [
|
|
48
|
+
eq(schema.contentDatabaseItems.documentId, document.id),
|
|
49
|
+
isNull(schema.contentDatabases.deletedAt),
|
|
50
|
+
];
|
|
51
|
+
if (options.databaseId) {
|
|
52
|
+
membershipClauses.push(
|
|
53
|
+
eq(schema.contentDatabaseItems.databaseId, options.databaseId),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
46
56
|
const [membership] = await db
|
|
47
57
|
.select({ database: schema.contentDatabases })
|
|
48
58
|
.from(schema.contentDatabaseItems)
|
|
@@ -50,12 +60,7 @@ export async function getDocumentContextPath(
|
|
|
50
60
|
schema.contentDatabases,
|
|
51
61
|
eq(schema.contentDatabases.id, schema.contentDatabaseItems.databaseId),
|
|
52
62
|
)
|
|
53
|
-
.where(
|
|
54
|
-
and(
|
|
55
|
-
eq(schema.contentDatabaseItems.documentId, document.id),
|
|
56
|
-
isNull(schema.contentDatabases.deletedAt),
|
|
57
|
-
),
|
|
58
|
-
)
|
|
63
|
+
.where(and(...membershipClauses))
|
|
59
64
|
.orderBy(
|
|
60
65
|
sql`CASE WHEN ${schema.contentDatabases.systemRole} IS NULL THEN 0 ELSE 1 END`,
|
|
61
66
|
asc(schema.contentDatabases.id),
|
|
@@ -36,6 +36,7 @@ export interface Document {
|
|
|
36
36
|
properties?: DocumentProperty[];
|
|
37
37
|
database?: ContentDatabase;
|
|
38
38
|
databaseMembership?: ContentDatabaseMembership;
|
|
39
|
+
bodyHydration?: ContentDocumentBodyHydration;
|
|
39
40
|
contextPath?: ContentContextPathEntry[];
|
|
40
41
|
createdAt: string;
|
|
41
42
|
updatedAt: string;
|
|
@@ -407,6 +408,13 @@ export interface ContentDatabaseMembership {
|
|
|
407
408
|
bodyHydration?: ContentDatabaseBodyHydration;
|
|
408
409
|
}
|
|
409
410
|
|
|
411
|
+
export interface ContentDocumentBodyHydration {
|
|
412
|
+
provider?: "builder";
|
|
413
|
+
hydration?: ContentDatabaseBodyHydration;
|
|
414
|
+
sourceId?: string;
|
|
415
|
+
databaseDocumentId?: string;
|
|
416
|
+
}
|
|
417
|
+
|
|
410
418
|
export type ContentDatabaseBodyHydrationState =
|
|
411
419
|
| "pending"
|
|
412
420
|
| "hydrating"
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* Body: { json: any, fieldName?: string, type?: "map"|"array", requestSource?: string }
|
|
14
14
|
*/
|
|
15
15
|
export declare const postCollabJson: import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
16
|
-
error: string;
|
|
17
16
|
ok?: undefined;
|
|
17
|
+
error: string;
|
|
18
18
|
} | {
|
|
19
19
|
error?: undefined;
|
|
20
20
|
ok: boolean;
|
|
@@ -16,19 +16,19 @@ export declare function createNotificationsHandler(): import("h3").EventHandlerW
|
|
|
16
16
|
error?: undefined;
|
|
17
17
|
ok?: undefined;
|
|
18
18
|
} | {
|
|
19
|
-
count?: undefined;
|
|
20
19
|
updated: number;
|
|
21
20
|
error?: undefined;
|
|
22
21
|
ok?: undefined;
|
|
23
|
-
} | {
|
|
24
22
|
count?: undefined;
|
|
23
|
+
} | {
|
|
25
24
|
updated?: undefined;
|
|
26
25
|
error: string;
|
|
27
26
|
ok?: undefined;
|
|
28
|
-
} | {
|
|
29
27
|
count?: undefined;
|
|
28
|
+
} | {
|
|
30
29
|
updated?: undefined;
|
|
31
30
|
ok: boolean;
|
|
32
31
|
error?: undefined;
|
|
32
|
+
count?: undefined;
|
|
33
33
|
}>>;
|
|
34
34
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -75,6 +75,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
75
75
|
user: "user";
|
|
76
76
|
}>>;
|
|
77
77
|
}, z.core.$strip>>, {
|
|
78
|
+
message?: undefined;
|
|
79
|
+
id?: undefined;
|
|
78
80
|
found?: undefined;
|
|
79
81
|
deleted?: undefined;
|
|
80
82
|
providers: {
|
|
@@ -89,20 +91,19 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
89
91
|
count: number;
|
|
90
92
|
provider?: undefined;
|
|
91
93
|
registered?: undefined;
|
|
92
|
-
id?: undefined;
|
|
93
94
|
label?: undefined;
|
|
94
|
-
message?: undefined;
|
|
95
95
|
} | {
|
|
96
|
+
message?: undefined;
|
|
97
|
+
id?: undefined;
|
|
96
98
|
deleted?: undefined;
|
|
97
99
|
providers?: undefined;
|
|
98
100
|
count?: undefined;
|
|
99
101
|
found: boolean;
|
|
100
102
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
101
103
|
registered?: undefined;
|
|
102
|
-
id?: undefined;
|
|
103
104
|
label?: undefined;
|
|
104
|
-
message?: undefined;
|
|
105
105
|
} | {
|
|
106
|
+
message?: undefined;
|
|
106
107
|
deleted?: undefined;
|
|
107
108
|
providers?: undefined;
|
|
108
109
|
count?: undefined;
|
|
@@ -111,8 +112,8 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
111
112
|
id: string;
|
|
112
113
|
registered?: undefined;
|
|
113
114
|
label?: undefined;
|
|
114
|
-
message?: undefined;
|
|
115
115
|
} | {
|
|
116
|
+
message?: undefined;
|
|
116
117
|
found?: undefined;
|
|
117
118
|
providers?: undefined;
|
|
118
119
|
count?: undefined;
|
|
@@ -121,7 +122,6 @@ export declare function createCustomProviderRegistrationAction<TSchema extends Z
|
|
|
121
122
|
id: string;
|
|
122
123
|
registered?: undefined;
|
|
123
124
|
label?: undefined;
|
|
124
|
-
message?: undefined;
|
|
125
125
|
} | {
|
|
126
126
|
found?: undefined;
|
|
127
127
|
deleted?: undefined;
|
|
@@ -311,9 +311,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
311
311
|
notes?: string | undefined;
|
|
312
312
|
scope?: "org" | "user" | undefined;
|
|
313
313
|
}, {
|
|
314
|
-
deleted?: undefined;
|
|
315
314
|
id?: undefined;
|
|
316
315
|
provider?: undefined;
|
|
316
|
+
deleted?: undefined;
|
|
317
317
|
found?: undefined;
|
|
318
318
|
providers: {
|
|
319
319
|
id: string;
|
|
@@ -329,9 +329,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
329
329
|
label?: undefined;
|
|
330
330
|
message?: undefined;
|
|
331
331
|
} | {
|
|
332
|
-
deleted?: undefined;
|
|
333
332
|
count?: undefined;
|
|
334
333
|
id?: undefined;
|
|
334
|
+
deleted?: undefined;
|
|
335
335
|
providers?: undefined;
|
|
336
336
|
found: boolean;
|
|
337
337
|
provider: import("../custom-registry.js").CustomProviderConfig;
|
|
@@ -339,9 +339,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
339
339
|
label?: undefined;
|
|
340
340
|
message?: undefined;
|
|
341
341
|
} | {
|
|
342
|
-
deleted?: undefined;
|
|
343
342
|
count?: undefined;
|
|
344
343
|
provider?: undefined;
|
|
344
|
+
deleted?: undefined;
|
|
345
345
|
providers?: undefined;
|
|
346
346
|
found: boolean;
|
|
347
347
|
id: string;
|
|
@@ -359,9 +359,9 @@ export declare function createProviderApiActions(runtime: Pick<ProviderApiRuntim
|
|
|
359
359
|
label?: undefined;
|
|
360
360
|
message?: undefined;
|
|
361
361
|
} | {
|
|
362
|
-
deleted?: undefined;
|
|
363
362
|
count?: undefined;
|
|
364
363
|
provider?: undefined;
|
|
364
|
+
deleted?: undefined;
|
|
365
365
|
found?: undefined;
|
|
366
366
|
providers?: undefined;
|
|
367
367
|
registered: boolean;
|
package/dist/secrets/routes.d.ts
CHANGED
|
@@ -34,37 +34,37 @@ export declare function createListSecretsHandler(): import("h3").EventHandlerWit
|
|
|
34
34
|
/** POST /_agent-native/secrets/:key — write a secret. */
|
|
35
35
|
export declare function createWriteSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
36
36
|
error: string;
|
|
37
|
-
ok?: undefined;
|
|
38
37
|
status?: undefined;
|
|
38
|
+
ok?: undefined;
|
|
39
39
|
} | {
|
|
40
|
-
error?: undefined;
|
|
41
40
|
ok: boolean;
|
|
42
41
|
status: string;
|
|
42
|
+
error?: undefined;
|
|
43
43
|
} | {
|
|
44
|
-
ok?: undefined;
|
|
45
44
|
error: string;
|
|
46
45
|
removed?: undefined;
|
|
46
|
+
ok?: undefined;
|
|
47
47
|
} | {
|
|
48
|
-
error?: undefined;
|
|
49
48
|
ok: boolean;
|
|
50
49
|
removed: boolean;
|
|
50
|
+
error?: undefined;
|
|
51
51
|
}>>;
|
|
52
52
|
/**
|
|
53
53
|
* POST /_agent-native/secrets/:key/test — re-run the validator against the
|
|
54
54
|
* current stored value without changing anything. Useful for the "Test" button.
|
|
55
55
|
*/
|
|
56
56
|
export declare function createTestSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<{
|
|
57
|
-
ok?: undefined;
|
|
58
57
|
error: string;
|
|
59
58
|
note?: undefined;
|
|
59
|
+
ok?: undefined;
|
|
60
60
|
} | {
|
|
61
|
-
error?: undefined;
|
|
62
61
|
ok: boolean;
|
|
63
62
|
note?: undefined;
|
|
64
|
-
} | {
|
|
65
63
|
error?: undefined;
|
|
64
|
+
} | {
|
|
66
65
|
ok: boolean;
|
|
67
66
|
note: string;
|
|
67
|
+
error?: undefined;
|
|
68
68
|
} | {
|
|
69
69
|
note?: undefined;
|
|
70
70
|
ok: boolean;
|
|
@@ -95,12 +95,12 @@ export interface AdHocSecretPayload {
|
|
|
95
95
|
export declare function createAdHocSecretHandler(): import("h3").EventHandlerWithFetch<import("h3").EventHandlerRequest, Promise<AdHocSecretPayload[] | {
|
|
96
96
|
error: string;
|
|
97
97
|
} | {
|
|
98
|
-
error?: undefined;
|
|
99
98
|
ok: boolean;
|
|
100
99
|
key: string;
|
|
101
|
-
} | {
|
|
102
100
|
error?: undefined;
|
|
101
|
+
} | {
|
|
103
102
|
ok: boolean;
|
|
104
103
|
removed: boolean;
|
|
104
|
+
error?: undefined;
|
|
105
105
|
}>>;
|
|
106
106
|
//# sourceMappingURL=routes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAoB,KAAK,GAAG,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"access.d.ts","sourceRoot":"","sources":["../../src/sharing/access.ts"],"names":[],"mappings":"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"}
|
package/dist/sharing/access.js
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
* callers who lack the required role.
|
|
14
14
|
*/
|
|
15
15
|
import { and, eq, or, sql } from "drizzle-orm";
|
|
16
|
+
import { orgMembers } from "../org/schema.js";
|
|
16
17
|
import { getRequestAuthCapability, getRequestUserEmail, getRequestOrgId, } from "../server/request-context.js";
|
|
17
18
|
import { listShareableResources, requireShareableResource, } from "./registry.js";
|
|
18
19
|
import { ROLE_RANK } from "./schema.js";
|
|
@@ -61,6 +62,25 @@ function normalizeEmailForAccess(email) {
|
|
|
61
62
|
function emailColumnMatches(column, email) {
|
|
62
63
|
return sql `lower(${column}) = ${email}`;
|
|
63
64
|
}
|
|
65
|
+
/**
|
|
66
|
+
* Real `org_members` membership, independent of the caller's currently
|
|
67
|
+
* active org (`ctx.orgId`). `org`-visibility access must key off actual
|
|
68
|
+
* membership — a user's active-org selection is a UI convenience, not a
|
|
69
|
+
* statement of which orgs they belong to.
|
|
70
|
+
*
|
|
71
|
+
* Queries through the resource's own `reg.getDb()` — the same connection
|
|
72
|
+
* every other lookup in this file uses — not the ambient `getDbExec()`,
|
|
73
|
+
* since `org_members` lives in that same app database.
|
|
74
|
+
*/
|
|
75
|
+
async function isOrgMember(reg, memberOrgId, email) {
|
|
76
|
+
const db = reg.getDb();
|
|
77
|
+
const rows = await db
|
|
78
|
+
.select({ id: orgMembers.id })
|
|
79
|
+
.from(orgMembers)
|
|
80
|
+
.where(and(eq(orgMembers.orgId, memberOrgId), emailColumnMatches(orgMembers.email, email)))
|
|
81
|
+
.limit(1);
|
|
82
|
+
return rows.length > 0;
|
|
83
|
+
}
|
|
64
84
|
/**
|
|
65
85
|
* Build a Drizzle `WHERE` clause that admits rows the current user can see.
|
|
66
86
|
* Pass the ownable resource table and its shares table; optional min role
|
|
@@ -284,7 +304,7 @@ async function resolveAccessImpl(resourceType, resourceId, rawCtx = currentAcces
|
|
|
284
304
|
const resource = await loadResourceForAccess(reg, resourceId, options);
|
|
285
305
|
if (!resource)
|
|
286
306
|
return null;
|
|
287
|
-
const { userEmail
|
|
307
|
+
const { userEmail } = ctx;
|
|
288
308
|
const normalizedUserEmail = normalizeEmailForAccess(userEmail);
|
|
289
309
|
if (normalizedUserEmail &&
|
|
290
310
|
normalizeEmailForAccess(resource.ownerEmail) === normalizedUserEmail &&
|
|
@@ -302,7 +322,17 @@ async function resolveAccessImpl(resourceType, resourceId, rawCtx = currentAcces
|
|
|
302
322
|
// `visibility === "public"` on an `allowPublic: false` resource is treated
|
|
303
323
|
// as private: only owner + explicit shares grant access. Falls through to
|
|
304
324
|
// the explicit-share lookup below.
|
|
305
|
-
|
|
325
|
+
//
|
|
326
|
+
// Membership in the resource's own org, not equality with the caller's
|
|
327
|
+
// currently active org: a caller can be a genuine member of the
|
|
328
|
+
// resource's org while a *different* org is their active selection, and
|
|
329
|
+
// `org` visibility should still admit them. Still requires some active
|
|
330
|
+
// org to be set at all (`orgId`), matching the pre-existing behavior for
|
|
331
|
+
// a caller with no active org.
|
|
332
|
+
if (resource.visibility === "org" &&
|
|
333
|
+
resource.orgId &&
|
|
334
|
+
normalizedUserEmail &&
|
|
335
|
+
(await isOrgMember(reg, resource.orgId, normalizedUserEmail))) {
|
|
306
336
|
const role = await highestShareRole(reg, resourceId, ctx, resource);
|
|
307
337
|
return { role: role ?? "viewer", resource };
|
|
308
338
|
}
|