@milaboratories/pl-client 3.14.6 → 3.15.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/core/PromiseTracker.d.ts +1 -2
- package/dist/core/PromiseTracker.d.ts.map +1 -1
- package/dist/core/abstract_stream.d.ts +2 -3
- package/dist/core/abstract_stream.d.ts.map +1 -1
- package/dist/core/auth.d.ts +2 -4
- package/dist/core/auth.d.ts.map +1 -1
- package/dist/core/cache.d.ts +1 -3
- package/dist/core/cache.d.ts.map +1 -1
- package/dist/core/capabilities.d.ts +2 -3
- package/dist/core/capabilities.d.ts.map +1 -1
- package/dist/core/client.cjs +7 -0
- package/dist/core/client.cjs.map +1 -1
- package/dist/core/client.d.ts +8 -5
- package/dist/core/client.d.ts.map +1 -1
- package/dist/core/client.js +7 -0
- package/dist/core/client.js.map +1 -1
- package/dist/core/config.d.ts +29 -31
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/default_client.d.ts +2 -4
- package/dist/core/default_client.d.ts.map +1 -1
- package/dist/core/driver.d.ts +4 -6
- package/dist/core/driver.d.ts.map +1 -1
- package/dist/core/errors.d.ts +20 -22
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/final.d.ts +4 -5
- package/dist/core/final.d.ts.map +1 -1
- package/dist/core/ll_client.cjs +10 -0
- package/dist/core/ll_client.cjs.map +1 -1
- package/dist/core/ll_client.d.ts +9 -5
- package/dist/core/ll_client.d.ts.map +1 -1
- package/dist/core/ll_client.js +10 -0
- package/dist/core/ll_client.js.map +1 -1
- package/dist/core/ll_transaction.d.ts +4 -6
- package/dist/core/ll_transaction.d.ts.map +1 -1
- package/dist/core/stat.d.ts +2 -3
- package/dist/core/stat.d.ts.map +1 -1
- package/dist/core/transaction.d.ts +30 -32
- package/dist/core/transaction.d.ts.map +1 -1
- package/dist/core/tree_filter.d.ts +14 -10
- package/dist/core/tree_filter.d.ts.map +1 -1
- package/dist/core/types.d.ts +54 -55
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/unauth_client.d.ts +4 -6
- package/dist/core/unauth_client.d.ts.map +1 -1
- package/dist/core/user_resources.cjs +23 -0
- package/dist/core/user_resources.cjs.map +1 -1
- package/dist/core/user_resources.d.ts +33 -7
- package/dist/core/user_resources.d.ts.map +1 -1
- package/dist/core/user_resources.js +23 -0
- package/dist/core/user_resources.js.map +1 -1
- package/dist/core/wire.d.ts +5 -7
- package/dist/core/wire.d.ts.map +1 -1
- package/dist/helpers/pl.d.ts +32 -34
- package/dist/helpers/pl.d.ts.map +1 -1
- package/dist/helpers/poll.d.ts +8 -9
- package/dist/helpers/poll.d.ts.map +1 -1
- package/dist/helpers/tx_helpers.d.ts +2 -4
- package/dist/helpers/tx_helpers.d.ts.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts +2 -4
- package/dist/proto-grpc/github.com/googleapis/googleapis/google/rpc/status.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs +163 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs +19 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts +29 -5
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js +19 -3
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts +2217 -428
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js +162 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.js.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts +51 -30
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts +7 -9
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts +6 -8
- package/dist/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/any.d.ts +2 -4
- package/dist/proto-grpc/google/protobuf/any.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/duration.d.ts +2 -4
- package/dist/proto-grpc/google/protobuf/duration.d.ts.map +1 -1
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts +2 -4
- package/dist/proto-grpc/google/protobuf/timestamp.d.ts.map +1 -1
- package/dist/proto-grpc/google/rpc/code.d.ts +1 -2
- package/dist/proto-grpc/google/rpc/code.d.ts.map +1 -1
- package/dist/proto-rest/index.d.ts +6 -8
- package/dist/proto-rest/index.d.ts.map +1 -1
- package/dist/proto-rest/plapi.d.ts +241 -122
- package/dist/proto-rest/plapi.d.ts.map +1 -1
- package/dist/test/tcp-proxy.d.ts +3 -5
- package/dist/test/tcp-proxy.d.ts.map +1 -1
- package/dist/test/test_config.d.ts +13 -14
- package/dist/test/test_config.d.ts.map +1 -1
- package/package.json +7 -7
- package/src/core/client.ts +9 -0
- package/src/core/ll_client.ts +15 -0
- package/src/core/user_resources.ts +39 -0
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +34 -3
- package/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +206 -0
package/dist/core/types.d.ts
CHANGED
|
@@ -1,34 +1,33 @@
|
|
|
1
1
|
import { Branded } from "@milaboratories/pl-model-common";
|
|
2
|
-
|
|
3
2
|
//#region src/core/types.d.ts
|
|
4
3
|
/** Null resource id */
|
|
5
|
-
type NullResourceId = Branded<bigint, "null", "__resource_id__">;
|
|
4
|
+
export type NullResourceId = Branded<bigint, "null", "__resource_id__">;
|
|
6
5
|
/** Global resource id */
|
|
7
|
-
type GlobalResourceId = Branded<bigint, "global", "__resource_id__">;
|
|
6
|
+
export type GlobalResourceId = Branded<bigint, "global", "__resource_id__">;
|
|
8
7
|
/** Local resource id */
|
|
9
|
-
type LocalResourceId = Branded<bigint, "local", "__resource_id__">;
|
|
8
|
+
export type LocalResourceId = Branded<bigint, "local", "__resource_id__">;
|
|
10
9
|
/** Any non-null resource id */
|
|
11
|
-
type AnyResourceId = GlobalResourceId | LocalResourceId;
|
|
10
|
+
export type AnyResourceId = GlobalResourceId | LocalResourceId;
|
|
12
11
|
/** All possible resource flavours */
|
|
13
|
-
type OptionalAnyResourceId = NullResourceId | GlobalResourceId | LocalResourceId;
|
|
14
|
-
declare const NullResourceId: NullResourceId;
|
|
15
|
-
declare function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId;
|
|
16
|
-
type ResourceKind = "Structural" | "Value";
|
|
17
|
-
type FieldType = "Input" | "Output" | "Service" | "OTW" | "Dynamic" | "MTW";
|
|
18
|
-
type FutureFieldType = "Output" | "Input" | "Service";
|
|
19
|
-
type FieldStatus = "Empty" | "Assigned" | "Resolved";
|
|
20
|
-
interface ResourceType {
|
|
12
|
+
export type OptionalAnyResourceId = NullResourceId | GlobalResourceId | LocalResourceId;
|
|
13
|
+
export declare const NullResourceId: NullResourceId;
|
|
14
|
+
export declare function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId;
|
|
15
|
+
export type ResourceKind = "Structural" | "Value";
|
|
16
|
+
export type FieldType = "Input" | "Output" | "Service" | "OTW" | "Dynamic" | "MTW";
|
|
17
|
+
export type FutureFieldType = "Output" | "Input" | "Service";
|
|
18
|
+
export type FieldStatus = "Empty" | "Assigned" | "Resolved";
|
|
19
|
+
export interface ResourceType {
|
|
21
20
|
readonly name: string;
|
|
22
21
|
readonly version: string;
|
|
23
22
|
}
|
|
24
|
-
declare function resourceType(name: string, version: string): ResourceType;
|
|
25
|
-
declare function resourceTypeToString(rt: ResourceType): string;
|
|
26
|
-
declare function parseResourceType(str: string): ResourceType;
|
|
27
|
-
declare function resourceTypesEqual(type1: ResourceType, type2: ResourceType): boolean;
|
|
23
|
+
export declare function resourceType(name: string, version: string): ResourceType;
|
|
24
|
+
export declare function resourceTypeToString(rt: ResourceType): string;
|
|
25
|
+
export declare function parseResourceType(str: string): ResourceType;
|
|
26
|
+
export declare function resourceTypesEqual(type1: ResourceType, type2: ResourceType): boolean;
|
|
28
27
|
/** Color proof used for resource creation requests (alias for ResourceSignature). */
|
|
29
|
-
type ColorProof = ResourceSignature;
|
|
28
|
+
export type ColorProof = ResourceSignature;
|
|
30
29
|
/** Readonly fields here marks properties of resource that can't change according to pl's state machine. */
|
|
31
|
-
type BasicResourceData = {
|
|
30
|
+
export type BasicResourceData = {
|
|
32
31
|
readonly id: SignedResourceId;
|
|
33
32
|
readonly originalResourceId: OptionalSignedResourceId;
|
|
34
33
|
readonly kind: ResourceKind;
|
|
@@ -42,66 +41,66 @@ type BasicResourceData = {
|
|
|
42
41
|
* a robust criteria to determine resource is in final state. */
|
|
43
42
|
readonly final: boolean;
|
|
44
43
|
};
|
|
45
|
-
declare function extractBasicResourceData(rd: ResourceData): BasicResourceData;
|
|
46
|
-
declare const jsonToData: (data: unknown) => Buffer<ArrayBuffer>;
|
|
47
|
-
declare const resDataToJson: (res: ResourceData) => unknown;
|
|
48
|
-
type ResourceData = BasicResourceData & {
|
|
44
|
+
export declare function extractBasicResourceData(rd: ResourceData): BasicResourceData;
|
|
45
|
+
export declare const jsonToData: (data: unknown) => Buffer<ArrayBuffer>;
|
|
46
|
+
export declare const resDataToJson: (res: ResourceData) => unknown;
|
|
47
|
+
export type ResourceData = BasicResourceData & {
|
|
49
48
|
readonly fields: FieldData[];
|
|
50
49
|
};
|
|
51
|
-
declare function getField(r: ResourceData, name: string): FieldData;
|
|
52
|
-
type FieldData = {
|
|
50
|
+
export declare function getField(r: ResourceData, name: string): FieldData;
|
|
51
|
+
export type FieldData = {
|
|
53
52
|
readonly name: string;
|
|
54
53
|
readonly type: FieldType;
|
|
55
54
|
readonly status: FieldStatus;
|
|
56
55
|
readonly value: OptionalSignedResourceId;
|
|
57
|
-
readonly error: OptionalSignedResourceId;
|
|
56
|
+
readonly error: OptionalSignedResourceId;
|
|
57
|
+
/** True if value the fields points to is in final state. */
|
|
58
58
|
readonly valueIsFinal: boolean;
|
|
59
59
|
};
|
|
60
|
-
declare const MaxLocalId = 16777215;
|
|
61
|
-
declare const MaxTxId = 4294967295;
|
|
62
|
-
declare function isRootResourceId(id: bigint): boolean;
|
|
63
|
-
declare function isLocalResourceId(id: bigint | string): id is LocalResourceId;
|
|
64
|
-
declare function createLocalResourceId(isRoot: boolean, localCounterValue: number, localTxId: number): LocalResourceId;
|
|
65
|
-
declare function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): GlobalResourceId;
|
|
66
|
-
declare function extractTxId(localResourceId: LocalResourceId): number;
|
|
67
|
-
declare function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void;
|
|
68
|
-
declare function resourceIdToString(resourceId: OptionalAnyResourceId | OptionalSignedResourceId): string;
|
|
69
|
-
declare function resourceIdFromString(str: string): OptionalAnyResourceId | undefined;
|
|
70
|
-
declare function anyResourceIdToBigint(resourceId: bigint | SignedResourceId): bigint;
|
|
71
|
-
declare function stringifyWithResourceId(object: unknown): string;
|
|
60
|
+
export declare const MaxLocalId = 16777215;
|
|
61
|
+
export declare const MaxTxId = 4294967295;
|
|
62
|
+
export declare function isRootResourceId(id: bigint): boolean;
|
|
63
|
+
export declare function isLocalResourceId(id: bigint | string): id is LocalResourceId;
|
|
64
|
+
export declare function createLocalResourceId(isRoot: boolean, localCounterValue: number, localTxId: number): LocalResourceId;
|
|
65
|
+
export declare function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): GlobalResourceId;
|
|
66
|
+
export declare function extractTxId(localResourceId: LocalResourceId): number;
|
|
67
|
+
export declare function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void;
|
|
68
|
+
export declare function resourceIdToString(resourceId: OptionalAnyResourceId | OptionalSignedResourceId): string;
|
|
69
|
+
export declare function resourceIdFromString(str: string): OptionalAnyResourceId | undefined;
|
|
70
|
+
export declare function anyResourceIdToBigint(resourceId: bigint | SignedResourceId): bigint;
|
|
71
|
+
export declare function stringifyWithResourceId(object: unknown): string;
|
|
72
72
|
/** Opaque authorization signature attached to a resource. */
|
|
73
|
-
type ResourceSignature = Branded<Uint8Array, "ResourceSignature">;
|
|
73
|
+
export type ResourceSignature = Branded<Uint8Array, "ResourceSignature">;
|
|
74
74
|
/**
|
|
75
75
|
* Signed resource id is "<global ID>|<resource signature hex>", encoded as string
|
|
76
76
|
* (e.g. "NG:0x123EC|1234567890abcdef")
|
|
77
77
|
*/
|
|
78
|
-
type SignedResourceId = Branded<string, "signed", "__signed_resource_id__">;
|
|
79
|
-
type NullSignedResourceId = Branded<string, "null", "__signed_resource_id__">;
|
|
80
|
-
declare const NullSignedResourceId: NullSignedResourceId;
|
|
78
|
+
export type SignedResourceId = Branded<string, "signed", "__signed_resource_id__">;
|
|
79
|
+
export type NullSignedResourceId = Branded<string, "null", "__signed_resource_id__">;
|
|
80
|
+
export declare const NullSignedResourceId: NullSignedResourceId;
|
|
81
81
|
/** Nullable signed resource ID */
|
|
82
|
-
type OptionalSignedResourceId = NullSignedResourceId | SignedResourceId;
|
|
83
|
-
declare function isNullSignedResourceId(resourceId: bigint | string): resourceId is NullSignedResourceId;
|
|
84
|
-
declare function isNotNullSignedResourceId(resourceId: OptionalSignedResourceId): resourceId is SignedResourceId;
|
|
85
|
-
declare function ensureSignedResourceIdNotNull(resourceId: OptionalSignedResourceId): SignedResourceId;
|
|
86
|
-
declare function isSignedResourceId(resourceId: bigint | string): resourceId is SignedResourceId;
|
|
87
|
-
declare function setResourceSignaturesRequired(required: boolean): void;
|
|
82
|
+
export type OptionalSignedResourceId = NullSignedResourceId | SignedResourceId;
|
|
83
|
+
export declare function isNullSignedResourceId(resourceId: bigint | string): resourceId is NullSignedResourceId;
|
|
84
|
+
export declare function isNotNullSignedResourceId(resourceId: OptionalSignedResourceId): resourceId is SignedResourceId;
|
|
85
|
+
export declare function ensureSignedResourceIdNotNull(resourceId: OptionalSignedResourceId): SignedResourceId;
|
|
86
|
+
export declare function isSignedResourceId(resourceId: bigint | string): resourceId is SignedResourceId;
|
|
87
|
+
export declare function setResourceSignaturesRequired(required: boolean): void;
|
|
88
88
|
/** Validate a string as a SignedResourceId and return it with the branded type.
|
|
89
89
|
* Requires the format "<globalId>|<signatureHex>". A non-empty signature is
|
|
90
90
|
* required only when {@link areResourceSignaturesRequired} is true. */
|
|
91
|
-
declare function asSignedResourceId(str: string): SignedResourceId;
|
|
91
|
+
export declare function asSignedResourceId(str: string): SignedResourceId;
|
|
92
92
|
/** Encode resource signature to base64url for embedding in URL-based handles. */
|
|
93
|
-
declare function signatureToBase64Url(sig?: ResourceSignature): string;
|
|
93
|
+
export declare function signatureToBase64Url(sig?: ResourceSignature): string;
|
|
94
94
|
/** Cast raw bytes to a branded ResourceSignature, returning undefined for empty/missing input. */
|
|
95
|
-
declare function toResourceSignature(raw?: Uint8Array): ResourceSignature;
|
|
95
|
+
export declare function toResourceSignature(raw?: Uint8Array): ResourceSignature;
|
|
96
96
|
/** Decode base64url-encoded string back to a branded ResourceSignature. */
|
|
97
|
-
declare function base64UrlToSignature(str: string): ResourceSignature;
|
|
97
|
+
export declare function base64UrlToSignature(str: string): ResourceSignature;
|
|
98
98
|
/** Converts bigint global resource id and signature to a SignedResourceId string.
|
|
99
99
|
* Format: "<globalIdString>|<signatureHex>" */
|
|
100
|
-
declare function createSignedResourceId(globalId: bigint, signature?: ResourceSignature): SignedResourceId;
|
|
101
|
-
declare function parseSignedResourceId(resourceId: SignedResourceId): {
|
|
100
|
+
export declare function createSignedResourceId(globalId: bigint, signature?: ResourceSignature): SignedResourceId;
|
|
101
|
+
export declare function parseSignedResourceId(resourceId: SignedResourceId): {
|
|
102
102
|
globalId: GlobalResourceId;
|
|
103
103
|
signature: ResourceSignature;
|
|
104
104
|
};
|
|
105
105
|
//#endregion
|
|
106
|
-
export { AnyResourceId, BasicResourceData, ColorProof, FieldData, FieldStatus, FieldType, FutureFieldType, GlobalResourceId, LocalResourceId, MaxLocalId, MaxTxId, NullResourceId, NullSignedResourceId, OptionalAnyResourceId, OptionalSignedResourceId, ResourceData, ResourceKind, ResourceSignature, ResourceType, SignedResourceId, anyResourceIdToBigint, asSignedResourceId, base64UrlToSignature, checkLocalityOfResourceId, createGlobalResourceId, createLocalResourceId, createSignedResourceId, ensureSignedResourceIdNotNull, extractBasicResourceData, extractTxId, getField, isAnyResourceId, isLocalResourceId, isNotNullSignedResourceId, isNullSignedResourceId, isRootResourceId, isSignedResourceId, jsonToData, parseResourceType, parseSignedResourceId, resDataToJson, resourceIdFromString, resourceIdToString, resourceType, resourceTypeToString, resourceTypesEqual, setResourceSignaturesRequired, signatureToBase64Url, stringifyWithResourceId, toResourceSignature };
|
|
107
106
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/core/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/core/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/core/types.ts"],"mappings":";;;YAMY,iBAAiB;;YAGjB,mBAAmB;;YAGnB,kBAAkB;;YAGlB,gBAAgB,mBAAmB;;YAGnC,wBAAwB,iBAAiB,mBAAmB;qBAE3D,gBAAuB;wBAMpB,gBAAgB,qBAAqB,cAAc;YAMvD;YAEA;YAEA;YAEA;iBAEK;WACN;WACA;;wBAGK,aAAa,cAAc,kBAAkB;wBAI7C,qBAAqB,IAAI;wBAIzB,kBAAkB,cAAc;wBAKhC,mBAAmB,OAAO,cAAc,OAAO;;YAKnD,aAAa;;YAGb;WACD,IAAI;WACJ,oBAAoB;WAEpB,MAAM;WACN,MAAM;WAEN,OAAO;WAEP,OAAO;WAEP;WACA;WACA;;;WAIA;;wBAGK,yBAAyB,IAAI,eAAe;qBA2B/C,aAAU,kBAAiB,OAAA;qBAE3B,gBAAa,KAAS;YAEvB,eAAe;WAChB,QAAQ;;wBAGH,SAAS,GAAG,cAAc,eAAe;YAI7C;WACD;WACA,MAAM;WACN,QAAQ;WACR,OAAO;WACP,OAAO;;WAGP;;qBAeE;qBACA;wBAQG,iBAAiB;wBAIjB,kBAAkB,sBAAsB,MAAM;wBAQ9C,sBACd,iBACA,2BACA,oBACC;wBAca,uBAAuB,iBAAiB,qBAAqB;wBAI7D,YAAY,iBAAiB;wBAI7B,0BAA0B,YAAY,eAAe;wBAQrD,mBACd,YAAY,wBAAwB;wBA8BtB,qBAAqB,cAAc;wBAUnC,sBAAsB,qBAAqB;wBAS3C,wBAAwB;;YAS5B,oBAAoB,QAAQ;;;;;YAM5B,mBAAmB;YAEnB,uBAAuB;qBAEtB,sBAA6B;;YAG9B,2BAA2B,uBAAuB;wBAE9C,uBACd,8BACC,cAAc;wBAID,0BACd,YAAY,2BACX,cAAc;wBAID,8BACd,YAAY,2BACX;wBAKa,mBAAmB,8BAA8B,cAAc;wBAW/D,8BAA8B;;;;wBAO9B,mBAAmB,cAAc;;wBAUjC,qBAAqB,MAAM;;wBAK3B,oBAAoB,MAAM,aAAa;;wBAOvC,qBAAqB,cAAc;;;wBAMnC,uBACd,kBACA,YAAY,oBACX;wBASa,sBAAsB,YAAY;EAChD,UAAU;EACV,WAAW"}
|
|
@@ -3,15 +3,14 @@ import { AuthInformation, PlClientConfig } from "./config.js";
|
|
|
3
3
|
import { BackendCapability } from "./capabilities.js";
|
|
4
4
|
import { LLPlClient } from "./ll_client.js";
|
|
5
5
|
import { MiLogger } from "@milaboratories/ts-helpers";
|
|
6
|
-
|
|
7
6
|
//#region src/core/unauth_client.d.ts
|
|
8
7
|
/** Login-flow shape advertised by the backend for a given SSO method. Extension
|
|
9
8
|
* point: future flow variants (e.g. backend-driven) sit alongside `public_pkce`. */
|
|
10
|
-
type SSOFlowType = "public_pkce";
|
|
9
|
+
export type SSOFlowType = "public_pkce";
|
|
11
10
|
/** Typed projection of `AuthAPI.ListMethods.SSOAuthMethod` for desktop consumption.
|
|
12
11
|
* Carries everything needed to drive the PKCE flow + diagnostics fields the renderer
|
|
13
12
|
* may surface (`userIdClaim`, `groupsClaim`). */
|
|
14
|
-
type SSOAuthMethod = {
|
|
13
|
+
export type SSOAuthMethod = {
|
|
15
14
|
id: string;
|
|
16
15
|
description: string;
|
|
17
16
|
issuer: string;
|
|
@@ -31,7 +30,7 @@ type SSOAuthMethod = {
|
|
|
31
30
|
};
|
|
32
31
|
/** Server-issued material returned by {@link UnauthenticatedPlClient.beginSSOLogin}.
|
|
33
32
|
* Discriminated on `flow`; future flow variants extend the union. */
|
|
34
|
-
type SSOLoginAttempt = {
|
|
33
|
+
export type SSOLoginAttempt = {
|
|
35
34
|
flow: "public_pkce";
|
|
36
35
|
nonce: string;
|
|
37
36
|
expiresAt: Date;
|
|
@@ -42,7 +41,7 @@ type SSOLoginAttempt = {
|
|
|
42
41
|
clientSecret?: string;
|
|
43
42
|
};
|
|
44
43
|
/** Primarily used for initial authentication (login) */
|
|
45
|
-
declare class UnauthenticatedPlClient {
|
|
44
|
+
export declare class UnauthenticatedPlClient {
|
|
46
45
|
readonly ll: LLPlClient;
|
|
47
46
|
private constructor();
|
|
48
47
|
static build(configOrAddress: PlClientConfig | string, ops?: {
|
|
@@ -82,5 +81,4 @@ declare class UnauthenticatedPlClient {
|
|
|
82
81
|
}): Promise<AuthInformation>;
|
|
83
82
|
}
|
|
84
83
|
//#endregion
|
|
85
|
-
export { SSOAuthMethod, SSOFlowType, SSOLoginAttempt, UnauthenticatedPlClient };
|
|
86
84
|
//# sourceMappingURL=unauth_client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"unauth_client.d.ts","names":[],"sources":["../../src/core/unauth_client.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"unauth_client.d.ts","names":[],"sources":["../../src/core/unauth_client.ts"],"mappings":";;;;;;;;YAaY;;;;YAKA;EACV;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA,UAAU;;;;EAIV;;;;YAKU;EACV;EACA;EACA,WAAW;;;;;EAKX;;;qBAIW;WACK,IAAI;UAEb;SAIa,MAClB,iBAAiB,yBACjB;IAAQ,SAAS;MAChB,QAAQ;EAKE,QAAQ,QAAQ;EAIhB,eAAe,QAAQ;EAIvB,eAAe;EAIrB,cAAc,YAAY;;;;MAOtB;IAA0B;IAAgB;;;;EAW9C,aAAa;;;;;;;;;;;EAoCP,MAAM,cAAc,mBAAmB,QAAQ;;;EA4B/C,iBAAiB,QAAQ;;EAWzB,SAAS;IAAW,eAAe;MAAe,QAAQ"}
|
|
@@ -126,6 +126,29 @@ var UserResources = class {
|
|
|
126
126
|
async listUsers() {
|
|
127
127
|
return (await this.ll.listUsers()).map((user) => ({ login: user.login }));
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Deletes a user account: the record, every identity-index entry that resolves to it (its login,
|
|
131
|
+
* its email, and any alternative of either), every grant it holds, and its root resource — and
|
|
132
|
+
* with the root, everything still attached under it.
|
|
133
|
+
*
|
|
134
|
+
* Exists for the duplicate account one person can end up with — an identity that could not be
|
|
135
|
+
* matched across a provider cutover, or one minted before the backend started refusing two
|
|
136
|
+
* records for one identity. The spare record shows up wherever users are listed and its projects
|
|
137
|
+
* keep taking part in deduplication.
|
|
138
|
+
*
|
|
139
|
+
* Destructive and irreversible. Re-attach anything worth keeping to another user's root before
|
|
140
|
+
* calling — `pl-cli admin delete-user` does both steps in order. Requires admin/controller
|
|
141
|
+
* credentials; gRPC-only, like {@link listUsers}.
|
|
142
|
+
*/
|
|
143
|
+
async deleteUser(login) {
|
|
144
|
+
const resp = await this.ll.deleteUser(login);
|
|
145
|
+
return {
|
|
146
|
+
userRootId: resp.userRootId === 0n ? void 0 : resp.userRootId,
|
|
147
|
+
userRootDeleted: resp.userRootDeleted,
|
|
148
|
+
revokedGrants: resp.revokedGrants,
|
|
149
|
+
removedIdentityIndexEntries: resp.removedIdentityIndexEntries
|
|
150
|
+
};
|
|
151
|
+
}
|
|
129
152
|
async getUserRootViaRpc(opts = {}) {
|
|
130
153
|
const resp = await this.ll.getUserRoot({
|
|
131
154
|
login: opts.login,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.cjs","names":["isUnimplementedError","createSignedResourceId","toResourceSignature","ClientRoot","toGlobalResourceId","isNotNullSignedResourceId"],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user_resources.cjs","names":["isUnimplementedError","createSignedResourceId","toResourceSignature","ClientRoot","toGlobalResourceId","isNotNullSignedResourceId"],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** What {@link UserResources.deleteUser} removed, so a caller can report it. */\nexport interface UserDeletionReport {\n /** Raw id of the root resource the user owned, or undefined when they had none. Unsigned —\n * the resource is gone, so this is for the audit line, not for further calls. */\n readonly userRootId: bigint | undefined;\n /** Whether that root resource was deleted. */\n readonly userRootDeleted: boolean;\n /** Grants revoked, the user-root self-grant included. */\n readonly revokedGrants: number;\n /** Identity-index entries removed — one per value that resolved to the account: its login,\n * its email, and any alternative of either. */\n readonly removedIdentityIndexEntries: number;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n /**\n * Deletes a user account: the record, every identity-index entry that resolves to it (its login,\n * its email, and any alternative of either), every grant it holds, and its root resource — and\n * with the root, everything still attached under it.\n *\n * Exists for the duplicate account one person can end up with — an identity that could not be\n * matched across a provider cutover, or one minted before the backend started refusing two\n * records for one identity. The spare record shows up wherever users are listed and its projects\n * keep taking part in deduplication.\n *\n * Destructive and irreversible. Re-attach anything worth keeping to another user's root before\n * calling — `pl-cli admin delete-user` does both steps in order. Requires admin/controller\n * credentials; gRPC-only, like {@link listUsers}.\n */\n async deleteUser(login: string): Promise<UserDeletionReport> {\n const resp = await this.ll.deleteUser(login);\n\n return {\n userRootId: resp.userRootId === 0n ? undefined : resp.userRootId,\n userRootDeleted: resp.userRootDeleted,\n revokedGrants: resp.revokedGrants,\n removedIdentityIndexEntries: resp.removedIdentityIndexEntries,\n };\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
import { OptionalSignedResourceId, ResourceType, SignedResourceId } from "./types.js";
|
|
2
2
|
import { PlTransaction } from "./transaction.js";
|
|
3
3
|
import { LLPlClient } from "./ll_client.js";
|
|
4
|
-
|
|
5
4
|
//#region src/core/user_resources.d.ts
|
|
6
5
|
/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */
|
|
7
|
-
interface GrantEntry {
|
|
6
|
+
export interface GrantEntry {
|
|
8
7
|
/** Login the grant targets. An everyone-grant surfaces here as the backend's
|
|
9
8
|
* everyone-sentinel — test with {@link isEveryoneUserLogin} (from "./transaction"); callers
|
|
10
9
|
* map it to "*". */
|
|
@@ -17,12 +16,25 @@ interface GrantEntry {
|
|
|
17
16
|
readonly grantedAt: number;
|
|
18
17
|
}
|
|
19
18
|
/** One user known to the server, as returned by {@link UserResources.listUsers}. */
|
|
20
|
-
interface UserEntry {
|
|
19
|
+
export interface UserEntry {
|
|
21
20
|
/** Stable identifier of the user; the grant target and `GetUserRoot` key. */
|
|
22
21
|
readonly login: string;
|
|
23
22
|
}
|
|
23
|
+
/** What {@link UserResources.deleteUser} removed, so a caller can report it. */
|
|
24
|
+
export interface UserDeletionReport {
|
|
25
|
+
/** Raw id of the root resource the user owned, or undefined when they had none. Unsigned —
|
|
26
|
+
* the resource is gone, so this is for the audit line, not for further calls. */
|
|
27
|
+
readonly userRootId: bigint | undefined;
|
|
28
|
+
/** Whether that root resource was deleted. */
|
|
29
|
+
readonly userRootDeleted: boolean;
|
|
30
|
+
/** Grants revoked, the user-root self-grant included. */
|
|
31
|
+
readonly revokedGrants: number;
|
|
32
|
+
/** Identity-index entries removed — one per value that resolved to the account: its login,
|
|
33
|
+
* its email, and any alternative of either. */
|
|
34
|
+
readonly removedIdentityIndexEntries: number;
|
|
35
|
+
}
|
|
24
36
|
/** Information about a single data library (LS storage). */
|
|
25
|
-
interface StorageInfo {
|
|
37
|
+
export interface StorageInfo {
|
|
26
38
|
/** Machine-stable identifier, e.g. "library". Used for filtering and map keys. */
|
|
27
39
|
readonly storageId: string;
|
|
28
40
|
/** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */
|
|
@@ -36,7 +48,7 @@ interface StorageInfo {
|
|
|
36
48
|
* Callback type for running transactions. Matches PlClient._withTx signature
|
|
37
49
|
* so the index can run transactions before PlClient is fully initialized.
|
|
38
50
|
*/
|
|
39
|
-
type TxRunner = <T>(name: string, writable: boolean, clientRoot: OptionalSignedResourceId, body: (tx: PlTransaction) => Promise<T>) => Promise<T>;
|
|
51
|
+
export type TxRunner = <T>(name: string, writable: boolean, clientRoot: OptionalSignedResourceId, body: (tx: PlTransaction) => Promise<T>) => Promise<T>;
|
|
40
52
|
/**
|
|
41
53
|
* Abstracts user resource discovery with backward compatibility.
|
|
42
54
|
*
|
|
@@ -46,7 +58,7 @@ type TxRunner = <T>(name: string, writable: boolean, clientRoot: OptionalSignedR
|
|
|
46
58
|
* 2. listUserResources RPC (streams all resources, picks userRoot)
|
|
47
59
|
* 3. Named resource lookup/creation via transaction (legacy)
|
|
48
60
|
*/
|
|
49
|
-
declare class UserResources {
|
|
61
|
+
export declare class UserResources {
|
|
50
62
|
private readonly ll;
|
|
51
63
|
private readonly runTx;
|
|
52
64
|
readonly authUser: string | null;
|
|
@@ -114,6 +126,21 @@ declare class UserResources {
|
|
|
114
126
|
* REST-connected client, so callers gate on the `userListing:v1` capability.
|
|
115
127
|
*/
|
|
116
128
|
listUsers(): Promise<UserEntry[]>;
|
|
129
|
+
/**
|
|
130
|
+
* Deletes a user account: the record, every identity-index entry that resolves to it (its login,
|
|
131
|
+
* its email, and any alternative of either), every grant it holds, and its root resource — and
|
|
132
|
+
* with the root, everything still attached under it.
|
|
133
|
+
*
|
|
134
|
+
* Exists for the duplicate account one person can end up with — an identity that could not be
|
|
135
|
+
* matched across a provider cutover, or one minted before the backend started refusing two
|
|
136
|
+
* records for one identity. The spare record shows up wherever users are listed and its projects
|
|
137
|
+
* keep taking part in deduplication.
|
|
138
|
+
*
|
|
139
|
+
* Destructive and irreversible. Re-attach anything worth keeping to another user's root before
|
|
140
|
+
* calling — `pl-cli admin delete-user` does both steps in order. Requires admin/controller
|
|
141
|
+
* credentials; gRPC-only, like {@link listUsers}.
|
|
142
|
+
*/
|
|
143
|
+
deleteUser(login: string): Promise<UserDeletionReport>;
|
|
117
144
|
private getUserRootViaRpc;
|
|
118
145
|
private getUserRootViaList;
|
|
119
146
|
private getDataLibrariesViaList;
|
|
@@ -121,5 +148,4 @@ declare class UserResources {
|
|
|
121
148
|
private getDataLibrariesViaLegacy;
|
|
122
149
|
}
|
|
123
150
|
//#endregion
|
|
124
|
-
export { GrantEntry, StorageInfo, TxRunner, UserEntry, UserResources };
|
|
125
151
|
//# sourceMappingURL=user_resources.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.d.ts","names":[],"sources":["../../src/core/user_resources.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"user_resources.d.ts","names":[],"sources":["../../src/core/user_resources.ts"],"mappings":";;;;;iBAmBiB;;;;WAIN;;WAEA;;WAEA;;WAEA;;;iBAIM;;WAEN;;;iBAIM;;;WAGN;;WAEA;;WAEA;;;WAGA;;;iBAIM;;WAEN;;WAEA;;WAEA,YAAY;;WAEZ,cAAc;;;;;;YAab,YAAY,GACtB,cACA,mBACA,YAAY,0BACZ,OAAO,IAAI,kBAAkB,QAAQ,OAClC,QAAQ;;;;;;;;;;qBAaA;mBAIQ;mBACA;WACD;UALV;EAER,YACmB,IAAI,YACJ,OAAO,UACR;;;;;;;;;EAWZ,YAAY;IAAQ;IAAgB;MAA4B,QAAQ;EACxE,YAAY;IAChB;IACA;MACE,QAAQ;UAUE;UAkCA;;;;;EA0BR,iBACJ;IAAQ;IAAgB;MACvB,QAAQ,oBAAoB;;;;;;;;;;;;EAkCzB,0BACJ,0BACA;IAAQ;MACP,QAAQ;;;;;;;;;;;;;;;EA4BL,WAAW,YAAY,mBAAmB,QAAQ;;;;;;;EAkBlD,aAAa,QAAQ;;;;;;;;;;;;;;;EAmBrB,WAAW,gBAAgB,QAAQ;UAW3B;UAyBA;UA4BA;UAoEA;UA+BA"}
|
|
@@ -126,6 +126,29 @@ var UserResources = class {
|
|
|
126
126
|
async listUsers() {
|
|
127
127
|
return (await this.ll.listUsers()).map((user) => ({ login: user.login }));
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* Deletes a user account: the record, every identity-index entry that resolves to it (its login,
|
|
131
|
+
* its email, and any alternative of either), every grant it holds, and its root resource — and
|
|
132
|
+
* with the root, everything still attached under it.
|
|
133
|
+
*
|
|
134
|
+
* Exists for the duplicate account one person can end up with — an identity that could not be
|
|
135
|
+
* matched across a provider cutover, or one minted before the backend started refusing two
|
|
136
|
+
* records for one identity. The spare record shows up wherever users are listed and its projects
|
|
137
|
+
* keep taking part in deduplication.
|
|
138
|
+
*
|
|
139
|
+
* Destructive and irreversible. Re-attach anything worth keeping to another user's root before
|
|
140
|
+
* calling — `pl-cli admin delete-user` does both steps in order. Requires admin/controller
|
|
141
|
+
* credentials; gRPC-only, like {@link listUsers}.
|
|
142
|
+
*/
|
|
143
|
+
async deleteUser(login) {
|
|
144
|
+
const resp = await this.ll.deleteUser(login);
|
|
145
|
+
return {
|
|
146
|
+
userRootId: resp.userRootId === 0n ? void 0 : resp.userRootId,
|
|
147
|
+
userRootDeleted: resp.userRootDeleted,
|
|
148
|
+
revokedGrants: resp.revokedGrants,
|
|
149
|
+
removedIdentityIndexEntries: resp.removedIdentityIndexEntries
|
|
150
|
+
};
|
|
151
|
+
}
|
|
129
152
|
async getUserRootViaRpc(opts = {}) {
|
|
130
153
|
const resp = await this.ll.getUserRoot({
|
|
131
154
|
login: opts.login,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"user_resources.js","names":[],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"user_resources.js","names":[],"sources":["../../src/core/user_resources.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { LLPlClient } from \"./ll_client\";\nimport type { PlTransaction } from \"./transaction\";\nimport { toGlobalResourceId } from \"./transaction\";\nimport type { OptionalSignedResourceId, SignedResourceId, ResourceType } from \"./types\";\nimport {\n createSignedResourceId,\n isNotNullSignedResourceId,\n NullSignedResourceId,\n toResourceSignature,\n} from \"./types\";\nimport { isUnimplementedError } from \"./errors\";\nimport { ClientRoot } from \"../helpers/pl\";\n\nconst AnonymousClientRoot = \"AnonymousRoot\";\nconst LsStorageTypePrefix = \"LS/\"; // implements ls API in particular storage\nconst LsProviderFieldPrefix = \"storage/\"; // provides access to storages list\n\n/** One recipient of a resource, as returned by {@link UserResources.listGrants}. */\nexport interface GrantEntry {\n /** Login the grant targets. An everyone-grant surfaces here as the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\"); callers\n * map it to \"*\". */\n readonly user: string;\n /** True for a writable grant (copy / collaboration), false for read-only. */\n readonly writable: boolean;\n /** Login of the user who created the grant. */\n readonly grantedBy: string;\n /** When the grant was created (ms epoch). */\n readonly grantedAt: number;\n}\n\n/** One user known to the server, as returned by {@link UserResources.listUsers}. */\nexport interface UserEntry {\n /** Stable identifier of the user; the grant target and `GetUserRoot` key. */\n readonly login: string;\n}\n\n/** What {@link UserResources.deleteUser} removed, so a caller can report it. */\nexport interface UserDeletionReport {\n /** Raw id of the root resource the user owned, or undefined when they had none. Unsigned —\n * the resource is gone, so this is for the audit line, not for further calls. */\n readonly userRootId: bigint | undefined;\n /** Whether that root resource was deleted. */\n readonly userRootDeleted: boolean;\n /** Grants revoked, the user-root self-grant included. */\n readonly revokedGrants: number;\n /** Identity-index entries removed — one per value that resolved to the account: its login,\n * its email, and any alternative of either. */\n readonly removedIdentityIndexEntries: number;\n}\n\n/** Information about a single data library (LS storage). */\nexport interface StorageInfo {\n /** Machine-stable identifier, e.g. \"library\". Used for filtering and map keys. */\n readonly storageId: string;\n /** Human-readable display name. For V1/legacy equals storageId; for V2 from resource JSON data. */\n readonly storageName: string;\n /** Signed resource ID for this storage resource. */\n readonly resourceId: SignedResourceId;\n /** Full resource type including correct version (\"1\" or \"2\"). */\n readonly resourceType: ResourceType;\n}\n\n/** V2 LsStorage resource JSON data shape (contract with backend). */\ninterface LsStorageV2Data {\n storageName: string;\n storageID: string;\n}\n\n/**\n * Callback type for running transactions. Matches PlClient._withTx signature\n * so the index can run transactions before PlClient is fully initialized.\n */\nexport type TxRunner = <T>(\n name: string,\n writable: boolean,\n clientRoot: OptionalSignedResourceId,\n body: (tx: PlTransaction) => Promise<T>,\n) => Promise<T>;\n\ntype BackendCapability = \"getUserRoot\" | \"listUserResources\" | \"legacy\";\n\n/**\n * Abstracts user resource discovery with backward compatibility.\n *\n * Detects backend capability on the first getUserRoot() call and remembers\n * the result. Three-tier fallback:\n * 1. getUserRoot RPC (newest, supports createIfNotExists)\n * 2. listUserResources RPC (streams all resources, picks userRoot)\n * 3. Named resource lookup/creation via transaction (legacy)\n */\nexport class UserResources {\n private backendCapability: BackendCapability | undefined;\n\n constructor(\n private readonly ll: LLPlClient,\n private readonly runTx: TxRunner,\n public readonly authUser: string | null,\n ) {}\n\n /**\n * Returns the user's root resource ID.\n *\n * On first call, detects backend capability by trying methods in order:\n * 1. getUserRoot RPC (newest)\n * 2. listUserResources RPC\n * 3. Named resource lookup/creation via transaction (legacy)\n */\n async getUserRoot(opts: { login?: string; createIfNotExists: true }): Promise<SignedResourceId>;\n async getUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n async getUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n if (this.backendCapability === undefined) {\n return await this.detectAndGetUserRoot(opts);\n }\n return await this.getUserRootWith(this.backendCapability, opts);\n }\n\n private async detectAndGetUserRoot(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async detectAndGetUserRoot(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async detectAndGetUserRoot(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n // 1. Try getUserRoot RPC\n try {\n const root = await this.getUserRootViaRpc(opts);\n this.backendCapability = \"getUserRoot\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 2. Try listUserResources\n try {\n const root = await this.getUserRootViaList(opts);\n this.backendCapability = \"listUserResources\";\n return root;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n }\n\n // 3. Legacy fallback\n this.backendCapability = \"legacy\";\n return await this.getUserRootViaLegacy(opts);\n }\n\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists: true },\n ): Promise<SignedResourceId>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts?: { login?: string; createIfNotExists?: boolean },\n ): Promise<SignedResourceId | undefined>;\n private async getUserRootWith(\n capability: BackendCapability,\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n switch (capability) {\n case \"getUserRoot\":\n return await this.getUserRootViaRpc(opts);\n case \"listUserResources\":\n return await this.getUserRootViaList(opts);\n case \"legacy\":\n return await this.getUserRootViaLegacy(opts);\n }\n }\n\n /**\n * Returns all data libraries the user has access to.\n * Always fetches fresh from the server (no caching).\n */\n async getDataLibraries(\n opts: { login?: string; createUserRootIfNotExists?: boolean } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n if (this.backendCapability === undefined) {\n // First call — detect backend capability\n try {\n const libs = await this.getDataLibrariesViaList(opts);\n // getUserRoot RPC doesn't return libraries, but listUserResources does;\n // record at least \"listUserResources\" so future getUserRoot calls don't re-detect.\n this.backendCapability = \"listUserResources\";\n return libs;\n } catch (err) {\n if (!isUnimplementedError(err)) throw err;\n this.backendCapability = \"legacy\";\n return await this.getDataLibrariesViaLegacy();\n }\n }\n\n // A server that supports getUserRoot definitely supports listUserResources.\n if (this.backendCapability !== \"legacy\") {\n return await this.getDataLibrariesViaList(opts);\n }\n return await this.getDataLibrariesViaLegacy();\n }\n\n /**\n * Discovers shared resources of a given type granted to the user, as signed resource ids.\n *\n * The public, type-filtered form of {@link getDataLibrariesViaList}: it polls the\n * `ListUserResources` gRPC stream and returns every `sharedResource` entry whose\n * `resourceType.name` matches `resourceTypeName`. Matching is by name only — the\n * version is ignored (permissive, survives schema bumps).\n *\n * gRPC-only: `ListUserResources` throws on a REST-connected client\n * ({@link LLPlClient.listUserResources}), so callers on REST get a thrown error.\n */\n async listSharedResourcesByType(\n resourceTypeName: string,\n opts: { login?: string } = {},\n ): Promise<SignedResourceId[]> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n const result: SignedResourceId[] = [];\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n if (!sr.resourceType) continue;\n if (sr.resourceType.name !== resourceTypeName) continue;\n result.push(createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature)));\n }\n return result;\n }\n\n /**\n * Enumerates the recipients of a single resource — the donor-side \"who did I\n * share with\" view.\n *\n * Takes a signed, writable resource handle: the backend gates grant listing on a\n * signed resource id with writable permission, so only the resource's owner can\n * call it. An everyone-grant surfaces with `user` matching the backend's\n * everyone-sentinel — test with {@link isEveryoneUserLogin} (from \"./transaction\");\n * the caller maps that to \"*\".\n *\n * Runs over a read transaction ({@link PlTransaction.listGrants}), so it works on\n * every wire protocol — including REST, where the standalone server-streaming\n * `ListGrants` RPC has no binding.\n */\n async listGrants(resourceId: SignedResourceId): Promise<GrantEntry[]> {\n const grants = await this.runTx(\"ListGrants\", false, NullSignedResourceId, (tx) =>\n tx.listGrants(resourceId),\n );\n return grants.map((grant) => ({\n user: grant.user,\n writable: grant.permissions?.writable ?? false,\n grantedBy: grant.grantedBy,\n grantedAt: Number(grant.grantedAt),\n }));\n }\n\n /**\n * Lists the logins of users known to the server, for the recipient picker. A user becomes\n * known on first login; provisioned users who have never logged in do not appear. gRPC-only\n * (the underlying {@link LLPlClient.listUsers} has no REST binding) — throws on a\n * REST-connected client, so callers gate on the `userListing:v1` capability.\n */\n async listUsers(): Promise<UserEntry[]> {\n const users = await this.ll.listUsers();\n return users.map((user) => ({ login: user.login }));\n }\n\n /**\n * Deletes a user account: the record, every identity-index entry that resolves to it (its login,\n * its email, and any alternative of either), every grant it holds, and its root resource — and\n * with the root, everything still attached under it.\n *\n * Exists for the duplicate account one person can end up with — an identity that could not be\n * matched across a provider cutover, or one minted before the backend started refusing two\n * records for one identity. The spare record shows up wherever users are listed and its projects\n * keep taking part in deduplication.\n *\n * Destructive and irreversible. Re-attach anything worth keeping to another user's root before\n * calling — `pl-cli admin delete-user` does both steps in order. Requires admin/controller\n * credentials; gRPC-only, like {@link listUsers}.\n */\n async deleteUser(login: string): Promise<UserDeletionReport> {\n const resp = await this.ll.deleteUser(login);\n\n return {\n userRootId: resp.userRootId === 0n ? undefined : resp.userRootId,\n userRootDeleted: resp.userRootDeleted,\n revokedGrants: resp.revokedGrants,\n removedIdentityIndexEntries: resp.removedIdentityIndexEntries,\n };\n }\n\n private async getUserRootViaRpc(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaRpc(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaRpc(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const resp = await this.ll.getUserRoot({\n login: opts.login,\n createIfNotExists: opts.createIfNotExists,\n });\n if (resp.userRoot === undefined) {\n if (!opts.createIfNotExists) return undefined;\n throw new Error(\"getUserRoot returned no userRoot entry\");\n }\n return createSignedResourceId(\n resp.userRoot.resourceId,\n toResourceSignature(resp.userRoot.resourceSignature),\n );\n }\n\n private async getUserRootViaList(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaList(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaList(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const responses = await this.ll.listUserResources({ login: opts.login, limit: 1 });\n for (const msg of responses) {\n if (msg.entry.oneofKind === \"userRoot\") {\n return createSignedResourceId(\n msg.entry.userRoot.resourceId,\n toResourceSignature(msg.entry.userRoot.resourceSignature),\n );\n }\n }\n\n if (opts.createIfNotExists) {\n throw new Error(\"listUserResources returned no userRoot entry\");\n }\n\n return undefined;\n }\n\n private async getDataLibrariesViaList(\n opts: { login?: string } = {},\n ): Promise<ReadonlyMap<string, StorageInfo>> {\n const responses = await this.ll.listUserResources({ login: opts.login });\n\n // Collect all LS/* shared resources, separating V1 and V2\n const v1Entries: StorageInfo[] = [];\n const v2ResourceIds: { resourceId: SignedResourceId; resourceType: ResourceType }[] = [];\n\n for (const msg of responses) {\n if (msg.entry.oneofKind !== \"sharedResource\") continue;\n const sr = msg.entry.sharedResource;\n\n if (!sr.resourceType) continue;\n const typeName = sr.resourceType.name;\n const typeVersion = sr.resourceType.version;\n if (!typeName.startsWith(LsStorageTypePrefix)) continue;\n\n const rId = createSignedResourceId(sr.resourceId, toResourceSignature(sr.resourceSignature));\n const rType: ResourceType = { name: typeName, version: typeVersion };\n\n if (typeVersion === \"2\") {\n v2ResourceIds.push({ resourceId: rId, resourceType: rType });\n } else {\n // V1 or unknown version: derive storageId from type name\n const storageId = typeName.substring(LsStorageTypePrefix.length);\n v1Entries.push({\n storageId,\n storageName: storageId,\n resourceId: rId,\n resourceType: rType,\n });\n }\n }\n\n // Read V2 resource data in a single transaction\n let v2Entries: StorageInfo[] = [];\n if (v2ResourceIds.length > 0) {\n v2Entries = await this.runTx(\n \"ReadLsStorageV2Data\",\n false,\n NullSignedResourceId,\n async (tx) => {\n const entries: StorageInfo[] = [];\n for (const { resourceId, resourceType } of v2ResourceIds) {\n const rd = await tx.getResourceData(resourceId, false);\n if (rd.data) {\n const v2Data = JSON.parse(Buffer.from(rd.data).toString(\"utf-8\")) as LsStorageV2Data;\n entries.push({\n storageId: v2Data.storageID,\n storageName: v2Data.storageName,\n resourceId,\n resourceType,\n });\n }\n }\n return entries;\n },\n );\n }\n\n const result = new Map<string, StorageInfo>();\n for (const entry of [...v1Entries, ...v2Entries]) {\n result.set(entry.storageId, entry);\n }\n return result;\n }\n\n private async getUserRootViaLegacy(opts: {\n login?: string;\n createIfNotExists: true;\n }): Promise<SignedResourceId>;\n private async getUserRootViaLegacy(opts?: {\n login?: string;\n createIfNotExists?: boolean;\n }): Promise<SignedResourceId | undefined>;\n private async getUserRootViaLegacy(\n opts: { login?: string; createIfNotExists?: boolean } = {},\n ): Promise<SignedResourceId | undefined> {\n const login = opts.login ?? this.authUser;\n const mainRootName =\n login === null ? AnonymousClientRoot : createHash(\"sha256\").update(login).digest(\"hex\");\n\n return await this.runTx(\"initialization\", true, NullSignedResourceId, async (tx) => {\n if (await tx.checkResourceNameExists(mainRootName)) {\n return await tx.getResourceByName(mainRootName);\n }\n\n if (!opts.createIfNotExists) {\n return undefined;\n }\n\n const mainRoot = tx.createRoot(ClientRoot);\n tx.setResourceName(mainRootName, mainRoot);\n await tx.commit();\n return await toGlobalResourceId(mainRoot);\n });\n }\n\n private async getDataLibrariesViaLegacy(): Promise<ReadonlyMap<string, StorageInfo>> {\n return await this.runTx(\"GetAvailableStorageIds\", false, NullSignedResourceId, async (tx) => {\n const lsProviderId = await tx.getResourceByName(\"LSProvider\");\n const provider = await tx.getResourceData(lsProviderId, true);\n\n const result = new Map<string, StorageInfo>();\n for (const field of provider.fields) {\n if (field.type !== \"Dynamic\" || !isNotNullSignedResourceId(field.value)) continue;\n if (!field.name.startsWith(LsProviderFieldPrefix)) continue;\n\n const storageId = field.name.substring(LsProviderFieldPrefix.length);\n result.set(storageId, {\n storageId,\n storageName: storageId,\n resourceId: field.value,\n resourceType: { name: `${LsStorageTypePrefix}${storageId}`, version: \"1\" },\n });\n }\n return result;\n });\n }\n}\n"],"mappings":";;;;;;AAcA,MAAM,sBAAsB;AAC5B,MAAM,sBAAsB;AAC5B,MAAM,wBAAwB;;;;;;;;;;AA4E9B,IAAa,gBAAb,MAA2B;CAIN;CACA;CACD;CALlB;CAEA,YACE,IACA,OACA,UACA;EAHiB,KAAA,KAAA;EACA,KAAA,QAAA;EACD,KAAA,WAAA;CACf;CAeH,MAAM,YACJ,OAAwD,CAAC,GAClB;EACvC,IAAI,KAAK,sBAAsB,KAAA,GAC7B,OAAO,MAAM,KAAK,qBAAqB,IAAI;EAE7C,OAAO,MAAM,KAAK,gBAAgB,KAAK,mBAAmB,IAAI;CAChE;CAUA,MAAc,qBACZ,OAAwD,CAAC,GAClB;EAEvC,IAAI;GACF,MAAM,OAAO,MAAM,KAAK,kBAAkB,IAAI;GAC9C,KAAK,oBAAoB;GACzB,OAAO;EACT,SAAS,KAAK;GACZ,IAAI,CAAC,qBAAqB,GAAG,GAAG,MAAM;EACxC;EAGA,IAAI;GACF,MAAM,OAAO,MAAM,KAAK,mBAAmB,IAAI;GAC/C,KAAK,oBAAoB;GACzB,OAAO;EACT,SAAS,KAAK;GACZ,IAAI,CAAC,qBAAqB,GAAG,GAAG,MAAM;EACxC;EAGA,KAAK,oBAAoB;EACzB,OAAO,MAAM,KAAK,qBAAqB,IAAI;CAC7C;CAUA,MAAc,gBACZ,YACA,OAAwD,CAAC,GAClB;EACvC,QAAQ,YAAR;GACE,KAAK,eACH,OAAO,MAAM,KAAK,kBAAkB,IAAI;GAC1C,KAAK,qBACH,OAAO,MAAM,KAAK,mBAAmB,IAAI;GAC3C,KAAK,UACH,OAAO,MAAM,KAAK,qBAAqB,IAAI;EAC/C;CACF;;;;;CAMA,MAAM,iBACJ,OAAgE,CAAC,GACtB;EAC3C,IAAI,KAAK,sBAAsB,KAAA,GAE7B,IAAI;GACF,MAAM,OAAO,MAAM,KAAK,wBAAwB,IAAI;GAGpD,KAAK,oBAAoB;GACzB,OAAO;EACT,SAAS,KAAK;GACZ,IAAI,CAAC,qBAAqB,GAAG,GAAG,MAAM;GACtC,KAAK,oBAAoB;GACzB,OAAO,MAAM,KAAK,0BAA0B;EAC9C;EAIF,IAAI,KAAK,sBAAsB,UAC7B,OAAO,MAAM,KAAK,wBAAwB,IAAI;EAEhD,OAAO,MAAM,KAAK,0BAA0B;CAC9C;;;;;;;;;;;;CAaA,MAAM,0BACJ,kBACA,OAA2B,CAAC,GACC;EAC7B,MAAM,YAAY,MAAM,KAAK,GAAG,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;EAEvE,MAAM,SAA6B,CAAC;EACpC,KAAK,MAAM,OAAO,WAAW;GAC3B,IAAI,IAAI,MAAM,cAAc,kBAAkB;GAC9C,MAAM,KAAK,IAAI,MAAM;GACrB,IAAI,CAAC,GAAG,cAAc;GACtB,IAAI,GAAG,aAAa,SAAS,kBAAkB;GAC/C,OAAO,KAAK,uBAAuB,GAAG,YAAY,oBAAoB,GAAG,iBAAiB,CAAC,CAAC;EAC9F;EACA,OAAO;CACT;;;;;;;;;;;;;;;CAgBA,MAAM,WAAW,YAAqD;EAIpE,QAAO,MAHc,KAAK,MAAM,cAAc,OAAA,KAA8B,OAC1E,GAAG,WAAW,UAAU,CAC1B,EAAA,CACc,KAAK,WAAW;GAC5B,MAAM,MAAM;GACZ,UAAU,MAAM,aAAa,YAAY;GACzC,WAAW,MAAM;GACjB,WAAW,OAAO,MAAM,SAAS;EACnC,EAAE;CACJ;;;;;;;CAQA,MAAM,YAAkC;EAEtC,QAAO,MADa,KAAK,GAAG,UAAU,EAAA,CACzB,KAAK,UAAU,EAAE,OAAO,KAAK,MAAM,EAAE;CACpD;;;;;;;;;;;;;;;CAgBA,MAAM,WAAW,OAA4C;EAC3D,MAAM,OAAO,MAAM,KAAK,GAAG,WAAW,KAAK;EAE3C,OAAO;GACL,YAAY,KAAK,eAAe,KAAK,KAAA,IAAY,KAAK;GACtD,iBAAiB,KAAK;GACtB,eAAe,KAAK;GACpB,6BAA6B,KAAK;EACpC;CACF;CAUA,MAAc,kBACZ,OAAwD,CAAC,GAClB;EACvC,MAAM,OAAO,MAAM,KAAK,GAAG,YAAY;GACrC,OAAO,KAAK;GACZ,mBAAmB,KAAK;EAC1B,CAAC;EACD,IAAI,KAAK,aAAa,KAAA,GAAW;GAC/B,IAAI,CAAC,KAAK,mBAAmB,OAAO,KAAA;GACpC,MAAM,IAAI,MAAM,wCAAwC;EAC1D;EACA,OAAO,uBACL,KAAK,SAAS,YACd,oBAAoB,KAAK,SAAS,iBAAiB,CACrD;CACF;CAUA,MAAc,mBACZ,OAAwD,CAAC,GAClB;EACvC,MAAM,YAAY,MAAM,KAAK,GAAG,kBAAkB;GAAE,OAAO,KAAK;GAAO,OAAO;EAAE,CAAC;EACjF,KAAK,MAAM,OAAO,WAChB,IAAI,IAAI,MAAM,cAAc,YAC1B,OAAO,uBACL,IAAI,MAAM,SAAS,YACnB,oBAAoB,IAAI,MAAM,SAAS,iBAAiB,CAC1D;EAIJ,IAAI,KAAK,mBACP,MAAM,IAAI,MAAM,8CAA8C;CAIlE;CAEA,MAAc,wBACZ,OAA2B,CAAC,GACe;EAC3C,MAAM,YAAY,MAAM,KAAK,GAAG,kBAAkB,EAAE,OAAO,KAAK,MAAM,CAAC;EAGvE,MAAM,YAA2B,CAAC;EAClC,MAAM,gBAAgF,CAAC;EAEvF,KAAK,MAAM,OAAO,WAAW;GAC3B,IAAI,IAAI,MAAM,cAAc,kBAAkB;GAC9C,MAAM,KAAK,IAAI,MAAM;GAErB,IAAI,CAAC,GAAG,cAAc;GACtB,MAAM,WAAW,GAAG,aAAa;GACjC,MAAM,cAAc,GAAG,aAAa;GACpC,IAAI,CAAC,SAAS,WAAW,mBAAmB,GAAG;GAE/C,MAAM,MAAM,uBAAuB,GAAG,YAAY,oBAAoB,GAAG,iBAAiB,CAAC;GAC3F,MAAM,QAAsB;IAAE,MAAM;IAAU,SAAS;GAAY;GAEnE,IAAI,gBAAgB,KAClB,cAAc,KAAK;IAAE,YAAY;IAAK,cAAc;GAAM,CAAC;QACtD;IAEL,MAAM,YAAY,SAAS,UAAU,CAA0B;IAC/D,UAAU,KAAK;KACb;KACA,aAAa;KACb,YAAY;KACZ,cAAc;IAChB,CAAC;GACH;EACF;EAGA,IAAI,YAA2B,CAAC;EAChC,IAAI,cAAc,SAAS,GACzB,YAAY,MAAM,KAAK,MACrB,uBACA,OAAA,IAEA,OAAO,OAAO;GACZ,MAAM,UAAyB,CAAC;GAChC,KAAK,MAAM,EAAE,YAAY,kBAAkB,eAAe;IACxD,MAAM,KAAK,MAAM,GAAG,gBAAgB,YAAY,KAAK;IACrD,IAAI,GAAG,MAAM;KACX,MAAM,SAAS,KAAK,MAAM,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC,SAAS,OAAO,CAAC;KAChE,QAAQ,KAAK;MACX,WAAW,OAAO;MAClB,aAAa,OAAO;MACpB;MACA;KACF,CAAC;IACH;GACF;GACA,OAAO;EACT,CACF;EAGF,MAAM,yBAAS,IAAI,IAAyB;EAC5C,KAAK,MAAM,SAAS,CAAC,GAAG,WAAW,GAAG,SAAS,GAC7C,OAAO,IAAI,MAAM,WAAW,KAAK;EAEnC,OAAO;CACT;CAUA,MAAc,qBACZ,OAAwD,CAAC,GAClB;EACvC,MAAM,QAAQ,KAAK,SAAS,KAAK;EACjC,MAAM,eACJ,UAAU,OAAO,sBAAsB,WAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,KAAK;EAExF,OAAO,MAAM,KAAK,MAAM,kBAAkB,MAAA,IAA4B,OAAO,OAAO;GAClF,IAAI,MAAM,GAAG,wBAAwB,YAAY,GAC/C,OAAO,MAAM,GAAG,kBAAkB,YAAY;GAGhD,IAAI,CAAC,KAAK,mBACR;GAGF,MAAM,WAAW,GAAG,WAAW,UAAU;GACzC,GAAG,gBAAgB,cAAc,QAAQ;GACzC,MAAM,GAAG,OAAO;GAChB,OAAO,MAAM,mBAAmB,QAAQ;EAC1C,CAAC;CACH;CAEA,MAAc,4BAAuE;EACnF,OAAO,MAAM,KAAK,MAAM,0BAA0B,OAAA,IAA6B,OAAO,OAAO;GAC3F,MAAM,eAAe,MAAM,GAAG,kBAAkB,YAAY;GAC5D,MAAM,WAAW,MAAM,GAAG,gBAAgB,cAAc,IAAI;GAE5D,MAAM,yBAAS,IAAI,IAAyB;GAC5C,KAAK,MAAM,SAAS,SAAS,QAAQ;IACnC,IAAI,MAAM,SAAS,aAAa,CAAC,0BAA0B,MAAM,KAAK,GAAG;IACzE,IAAI,CAAC,MAAM,KAAK,WAAW,qBAAqB,GAAG;IAEnD,MAAM,YAAY,MAAM,KAAK,UAAU,CAA4B;IACnE,OAAO,IAAI,WAAW;KACpB;KACA,aAAa;KACb,YAAY,MAAM;KAClB,cAAc;MAAE,MAAM,GAAG,sBAAsB;MAAa,SAAS;KAAI;IAC3E,CAAC;GACH;GACA,OAAO;EACT,CAAC;CACH;AACF"}
|