@opengeni/documents 0.3.3 → 0.5.3
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/google-drive.d.ts +12 -5
- package/dist/google-drive.js +30 -14
- package/dist/google-drive.js.map +1 -1
- package/dist/index.d.ts +13 -2
- package/dist/index.js +38 -6
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/google-drive.ts +46 -25
- package/src/index.ts +56 -10
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opengeni/documents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.3",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -36,10 +36,10 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@llamaindex/liteparse": "^1.5.3",
|
|
39
|
-
"@opengeni/config": "^0.10.
|
|
40
|
-
"@opengeni/contracts": "^0.
|
|
41
|
-
"@opengeni/db": "^0.27.
|
|
42
|
-
"@opengeni/storage": "^0.2.
|
|
39
|
+
"@opengeni/config": "^0.10.11",
|
|
40
|
+
"@opengeni/contracts": "^0.38.1",
|
|
41
|
+
"@opengeni/db": "^0.27.7",
|
|
42
|
+
"@opengeni/storage": "^0.2.64",
|
|
43
43
|
"drizzle-orm": "^0.45.2",
|
|
44
44
|
"openai": "6.36.0"
|
|
45
45
|
}
|
package/src/google-drive.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import type { ScopedKnowledgeScope } from "@opengeni/contracts";
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
2
|
+
import {
|
|
3
|
+
connectorDestinationDocumentAuthority,
|
|
4
|
+
resolveConnectorDocumentDestination,
|
|
5
|
+
type ConnectorDocumentDestination,
|
|
6
|
+
} from "@opengeni/contracts/connector-destinations";
|
|
7
|
+
import type { GoogleDriveSelectedSource } from "@opengeni/contracts/google-drive";
|
|
6
8
|
|
|
7
9
|
export const GOOGLE_DRIVE_PROVIDER_KEY = "google-drive" as const;
|
|
8
10
|
export const GOOGLE_DRIVE_FOLDER_MIME_TYPE = "application/vnd.google-apps.folder" as const;
|
|
@@ -232,36 +234,52 @@ export class GoogleDriveInventoryProviderError extends Error {
|
|
|
232
234
|
}
|
|
233
235
|
|
|
234
236
|
export function googleDriveKnowledgeScope(
|
|
235
|
-
|
|
236
|
-
workspaceId: string,
|
|
237
|
-
initiatingSubjectId: string,
|
|
237
|
+
destination: ConnectorDocumentDestination,
|
|
238
238
|
): ScopedKnowledgeScope {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
if (targetScope === "organization") {
|
|
239
|
+
const authority = connectorDestinationDocumentAuthority(destination);
|
|
240
|
+
if (authority.authorityKind === "organization") {
|
|
242
241
|
return { kind: "organization", workspaceId: null, subjectId: null };
|
|
243
242
|
}
|
|
244
|
-
if (
|
|
245
|
-
|
|
243
|
+
if (authority.authorityKind === "workspace") {
|
|
244
|
+
if (!authority.authorityWorkspaceId) {
|
|
245
|
+
throw new Error("workspace connector destination is missing workspace authority");
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
kind: "workspace",
|
|
249
|
+
workspaceId: authority.authorityWorkspaceId,
|
|
250
|
+
subjectId: null,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
if (!authority.authorityWorkspaceId || !authority.authoritySubjectId) {
|
|
254
|
+
throw new Error("personal connector destination is missing immutable authority");
|
|
246
255
|
}
|
|
247
|
-
// Google Drive connections are workspace-bound today. Preserve that boundary
|
|
248
|
-
// for personal knowledge rather than silently widening it across workspaces.
|
|
249
256
|
return {
|
|
250
257
|
kind: "personal",
|
|
251
|
-
workspaceId:
|
|
252
|
-
subjectId:
|
|
258
|
+
workspaceId: authority.authorityWorkspaceId,
|
|
259
|
+
subjectId: authority.authoritySubjectId,
|
|
253
260
|
};
|
|
254
261
|
}
|
|
255
262
|
|
|
256
263
|
export function googleDriveKnowledgeSourceIdentity(input: {
|
|
257
264
|
googlePermissionId: string;
|
|
258
|
-
source: Pick<GoogleDriveSelectedSource, "id" | "driveId" | "targetScope">;
|
|
265
|
+
source: Pick<GoogleDriveSelectedSource, "id" | "driveId" | "destination" | "targetScope">;
|
|
266
|
+
accountId?: string | undefined;
|
|
259
267
|
workspaceId: string;
|
|
260
|
-
|
|
268
|
+
connectionSubjectId?: string | null | undefined;
|
|
269
|
+
/** @deprecated Used only to validate old callers while source destinations migrate. */
|
|
270
|
+
initiatingSubjectId?: string | undefined;
|
|
261
271
|
}): GoogleDriveKnowledgeSourceIdentity {
|
|
262
272
|
const sourceId = driveId(input.source.id, "source.id");
|
|
263
273
|
const externalTenantId = normalizedGooglePermissionId(input.googlePermissionId);
|
|
264
274
|
const sourceDriveId = nullableDriveId(input.source.driveId, "source.driveId");
|
|
275
|
+
const destination = resolveConnectorDocumentDestination(input.source.destination, {
|
|
276
|
+
accountId: input.accountId ?? input.workspaceId,
|
|
277
|
+
workspaceId: input.workspaceId,
|
|
278
|
+
connectionSubjectId:
|
|
279
|
+
input.connectionSubjectId !== undefined
|
|
280
|
+
? input.connectionSubjectId
|
|
281
|
+
: (input.initiatingSubjectId ?? null),
|
|
282
|
+
});
|
|
265
283
|
return {
|
|
266
284
|
providerKey: GOOGLE_DRIVE_PROVIDER_KEY,
|
|
267
285
|
externalTenantId,
|
|
@@ -273,11 +291,7 @@ export function googleDriveKnowledgeSourceIdentity(input: {
|
|
|
273
291
|
? "google-drive-shared-drive"
|
|
274
292
|
: "google-drive-folder",
|
|
275
293
|
sourceUri: googleDriveSourceUri(sourceId),
|
|
276
|
-
scope: googleDriveKnowledgeScope(
|
|
277
|
-
input.source.targetScope,
|
|
278
|
-
input.workspaceId,
|
|
279
|
-
input.initiatingSubjectId,
|
|
280
|
-
),
|
|
294
|
+
scope: googleDriveKnowledgeScope(destination),
|
|
281
295
|
};
|
|
282
296
|
}
|
|
283
297
|
|
|
@@ -327,8 +341,11 @@ export function planGoogleDriveTransfer(
|
|
|
327
341
|
export async function inventoryGoogleDriveSource(input: {
|
|
328
342
|
googlePermissionId: string;
|
|
329
343
|
source: GoogleDriveSelectedSource;
|
|
344
|
+
accountId?: string | undefined;
|
|
330
345
|
workspaceId: string;
|
|
331
|
-
|
|
346
|
+
connectionSubjectId?: string | null | undefined;
|
|
347
|
+
/** @deprecated Used only to validate old callers while source destinations migrate. */
|
|
348
|
+
initiatingSubjectId?: string | undefined;
|
|
332
349
|
limits: GoogleDriveInventoryLimits;
|
|
333
350
|
listChildren: GoogleDriveListChildren;
|
|
334
351
|
checkpoint?: GoogleDriveInventoryCheckpoint | null;
|
|
@@ -339,8 +356,12 @@ export async function inventoryGoogleDriveSource(input: {
|
|
|
339
356
|
const source = googleDriveKnowledgeSourceIdentity({
|
|
340
357
|
googlePermissionId,
|
|
341
358
|
source: input.source,
|
|
359
|
+
...(input.accountId ? { accountId: input.accountId } : {}),
|
|
342
360
|
workspaceId: input.workspaceId,
|
|
343
|
-
|
|
361
|
+
...(input.connectionSubjectId !== undefined
|
|
362
|
+
? { connectionSubjectId: input.connectionSubjectId }
|
|
363
|
+
: {}),
|
|
364
|
+
...(input.initiatingSubjectId ? { initiatingSubjectId: input.initiatingSubjectId } : {}),
|
|
344
365
|
});
|
|
345
366
|
const checkpointIdentity: GoogleDriveInventoryCheckpointIdentity = {
|
|
346
367
|
googlePermissionId,
|
package/src/index.ts
CHANGED
|
@@ -136,6 +136,7 @@ export type DocumentAuthority = {
|
|
|
136
136
|
|
|
137
137
|
export type DocumentInventoryStatusCounts = Record<DocumentStatus, number>;
|
|
138
138
|
export type DocumentInventorySourceKindCounts = Record<KnowledgeSourceKind, number>;
|
|
139
|
+
export type DocumentInventoryAuthorityKindCounts = Record<DocumentAuthorityKind, number>;
|
|
139
140
|
|
|
140
141
|
export type DocumentInventory = {
|
|
141
142
|
baseCount: number;
|
|
@@ -149,6 +150,7 @@ export type DocumentInventory = {
|
|
|
149
150
|
visibleDocumentCount: number;
|
|
150
151
|
statusCounts: DocumentInventoryStatusCounts;
|
|
151
152
|
sourceKindCounts: DocumentInventorySourceKindCounts;
|
|
153
|
+
authorityKindCounts: DocumentInventoryAuthorityKindCounts;
|
|
152
154
|
latestUpdatedAt: string | null;
|
|
153
155
|
topics: Array<{ name: string; documentCount: number }>;
|
|
154
156
|
topicsTruncated: boolean;
|
|
@@ -709,6 +711,9 @@ export async function getDocumentInventory(
|
|
|
709
711
|
documentCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'document')::int`,
|
|
710
712
|
webCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'web')::int`,
|
|
711
713
|
otherCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.sourceKind} = 'other')::int`,
|
|
714
|
+
organizationAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'organization')::int`,
|
|
715
|
+
workspaceAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'workspace')::int`,
|
|
716
|
+
personalAuthorityCount: sql<number>`count(${schema.documents.id}) filter (where ${schema.documents.authorityKind} = 'personal')::int`,
|
|
712
717
|
latestUpdatedAt: sql<Date | null>`max(${schema.documents.updatedAt})`,
|
|
713
718
|
})
|
|
714
719
|
.from(schema.documents)
|
|
@@ -762,6 +767,11 @@ export async function getDocumentInventory(
|
|
|
762
767
|
web: Number(summary?.webCount ?? 0),
|
|
763
768
|
other: Number(summary?.otherCount ?? 0),
|
|
764
769
|
},
|
|
770
|
+
authorityKindCounts: {
|
|
771
|
+
organization: Number(summary?.organizationAuthorityCount ?? 0),
|
|
772
|
+
workspace: Number(summary?.workspaceAuthorityCount ?? 0),
|
|
773
|
+
personal: Number(summary?.personalAuthorityCount ?? 0),
|
|
774
|
+
},
|
|
765
775
|
latestUpdatedAt: documentInventoryTimestamp(summary?.latestUpdatedAt ?? null),
|
|
766
776
|
topics,
|
|
767
777
|
topicsTruncated: topicRows.length > topics.length,
|
|
@@ -927,7 +937,7 @@ export async function addDocumentToBase(
|
|
|
927
937
|
)
|
|
928
938
|
.limit(1);
|
|
929
939
|
if (existing) {
|
|
930
|
-
if (!documentMatchesAccess(existing, input.access)) {
|
|
940
|
+
if (!documentMatchesAccess(existing, input.workspaceId, input.access)) {
|
|
931
941
|
throw new Error(`Document not found: ${existing.id}`);
|
|
932
942
|
}
|
|
933
943
|
assertOrganizationDocumentAuthority(
|
|
@@ -1878,28 +1888,64 @@ function documentAccessConditions(
|
|
|
1878
1888
|
}
|
|
1879
1889
|
|
|
1880
1890
|
function documentMatchesAccess(
|
|
1881
|
-
document: Pick<
|
|
1891
|
+
document: Pick<
|
|
1892
|
+
DocumentAccessRecord,
|
|
1893
|
+
"authorityKind" | "authorityWorkspaceId" | "authoritySubjectId" | "agentAccess"
|
|
1894
|
+
>,
|
|
1895
|
+
workspaceId: string,
|
|
1882
1896
|
access: DocumentAccessFilter | undefined,
|
|
1883
1897
|
): boolean {
|
|
1884
1898
|
if (access?.agentOnly) {
|
|
1885
|
-
return document.agentAccess && canViewDocument(document, access.viewerSubjectId);
|
|
1899
|
+
return document.agentAccess && canViewDocument(document, access.viewerSubjectId, workspaceId);
|
|
1886
1900
|
}
|
|
1887
|
-
return canViewDocument(document, access?.viewerSubjectId);
|
|
1901
|
+
return canViewDocument(document, access?.viewerSubjectId, workspaceId);
|
|
1888
1902
|
}
|
|
1889
1903
|
|
|
1890
|
-
|
|
1904
|
+
function canonicalDocumentAuthoritySubject(value: unknown): string | undefined {
|
|
1905
|
+
if (typeof value !== "string") return undefined;
|
|
1906
|
+
const subjectId = cleanString(value);
|
|
1907
|
+
if (!subjectId || subjectId !== value) return undefined;
|
|
1908
|
+
if (new TextEncoder().encode(subjectId).byteLength > DOCUMENT_AUTHORITY_SUBJECT_MAX_BYTES) {
|
|
1909
|
+
return undefined;
|
|
1910
|
+
}
|
|
1911
|
+
return subjectId;
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
/**
|
|
1915
|
+
* Whether a single already-fetched document is readable in this workspace.
|
|
1916
|
+
*
|
|
1917
|
+
* Keep this compatibility predicate as strict as SQL/RLS: personal authority
|
|
1918
|
+
* remains anchored to its originating workspace, and unknown or incomplete
|
|
1919
|
+
* authority tuples deny instead of falling through as workspace-visible.
|
|
1920
|
+
*/
|
|
1891
1921
|
export function canViewDocument(
|
|
1892
|
-
document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId"
|
|
1922
|
+
document: Pick<DocumentAccessRecord, "authorityKind" | "authoritySubjectId"> & {
|
|
1923
|
+
authorityWorkspaceId?: string | null | undefined;
|
|
1924
|
+
},
|
|
1893
1925
|
viewerSubjectId: string | null | undefined,
|
|
1926
|
+
workspaceId?: string | null | undefined,
|
|
1894
1927
|
): boolean {
|
|
1895
|
-
|
|
1896
|
-
document.
|
|
1897
|
-
|
|
1898
|
-
);
|
|
1928
|
+
if (document.authorityKind === "organization") {
|
|
1929
|
+
return document.authorityWorkspaceId === null && document.authoritySubjectId === null;
|
|
1930
|
+
}
|
|
1931
|
+
const normalizedWorkspaceId = cleanString(workspaceId ?? null);
|
|
1932
|
+
if (!normalizedWorkspaceId || document.authorityWorkspaceId !== normalizedWorkspaceId) {
|
|
1933
|
+
return false;
|
|
1934
|
+
}
|
|
1935
|
+
if (document.authorityKind === "workspace") {
|
|
1936
|
+
return document.authoritySubjectId === null;
|
|
1937
|
+
}
|
|
1938
|
+
if (document.authorityKind === "personal") {
|
|
1939
|
+
const authoritySubjectId = canonicalDocumentAuthoritySubject(document.authoritySubjectId);
|
|
1940
|
+
const viewer = canonicalDocumentAuthoritySubject(viewerSubjectId);
|
|
1941
|
+
return !!authoritySubjectId && authoritySubjectId === viewer;
|
|
1942
|
+
}
|
|
1943
|
+
return false;
|
|
1899
1944
|
}
|
|
1900
1945
|
|
|
1901
1946
|
type DocumentAccessRecord = {
|
|
1902
1947
|
authorityKind: string;
|
|
1948
|
+
authorityWorkspaceId: string | null;
|
|
1903
1949
|
authoritySubjectId: string | null;
|
|
1904
1950
|
agentAccess: boolean;
|
|
1905
1951
|
};
|