@milaboratories/pl-client 2.4.10
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/README.md +52 -0
- package/dist/index.cjs +14527 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.js +14426 -0
- package/dist/index.js.map +1 -0
- package/package.json +49 -0
- package/src/core/auth.ts +27 -0
- package/src/core/client.test.ts +47 -0
- package/src/core/client.ts +302 -0
- package/src/core/config.test.ts +19 -0
- package/src/core/config.ts +197 -0
- package/src/core/default_client.ts +161 -0
- package/src/core/driver.ts +30 -0
- package/src/core/error.test.ts +14 -0
- package/src/core/errors.ts +84 -0
- package/src/core/http.ts +178 -0
- package/src/core/ll_client.test.ts +111 -0
- package/src/core/ll_client.ts +228 -0
- package/src/core/ll_transaction.test.ts +152 -0
- package/src/core/ll_transaction.ts +333 -0
- package/src/core/transaction.test.ts +173 -0
- package/src/core/transaction.ts +730 -0
- package/src/core/type_conversion.ts +121 -0
- package/src/core/types.test.ts +22 -0
- package/src/core/types.ts +223 -0
- package/src/core/unauth_client.test.ts +21 -0
- package/src/core/unauth_client.ts +48 -0
- package/src/helpers/pl.ts +141 -0
- package/src/helpers/poll.ts +178 -0
- package/src/helpers/rich_resource_types.test.ts +22 -0
- package/src/helpers/rich_resource_types.ts +84 -0
- package/src/helpers/smart_accessors.ts +146 -0
- package/src/helpers/state_helpers.ts +5 -0
- package/src/helpers/tx_helpers.ts +24 -0
- package/src/index.ts +14 -0
- package/src/proto/github.com/googleapis/googleapis/google/rpc/status.ts +125 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +45 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +271 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.client.ts +51 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +380 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +59 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +450 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client.ts +148 -0
- package/src/proto/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +706 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +406 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api.ts +12636 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +1384 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts +181 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/import.ts +251 -0
- package/src/proto/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts +693 -0
- package/src/proto/google/api/http.ts +687 -0
- package/src/proto/google/protobuf/any.ts +326 -0
- package/src/proto/google/protobuf/descriptor.ts +4502 -0
- package/src/proto/google/protobuf/duration.ts +230 -0
- package/src/proto/google/protobuf/empty.ts +81 -0
- package/src/proto/google/protobuf/struct.ts +482 -0
- package/src/proto/google/protobuf/timestamp.ts +287 -0
- package/src/proto/google/protobuf/wrappers.ts +751 -0
- package/src/test/test_config.test.ts +6 -0
- package/src/test/test_config.ts +166 -0
- package/src/util/branding.ts +4 -0
- package/src/util/pl.ts +11 -0
- package/src/util/util.test.ts +10 -0
- package/src/util/util.ts +9 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Field,
|
|
3
|
+
Field_ValueStatus,
|
|
4
|
+
Resource,
|
|
5
|
+
Resource_Kind
|
|
6
|
+
} from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api_types';
|
|
7
|
+
import { FieldType as GrpcFieldType } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/base_types';
|
|
8
|
+
import {
|
|
9
|
+
FieldData,
|
|
10
|
+
FieldStatus,
|
|
11
|
+
NullResourceId,
|
|
12
|
+
OptionalResourceId,
|
|
13
|
+
FieldType,
|
|
14
|
+
ResourceData,
|
|
15
|
+
ResourceId,
|
|
16
|
+
ResourceKind
|
|
17
|
+
} from './types';
|
|
18
|
+
import { assertNever, notEmpty } from '@milaboratories/ts-helpers';
|
|
19
|
+
import { throwPlNotFoundError } from './errors';
|
|
20
|
+
|
|
21
|
+
const ResourceErrorField = 'resourceError';
|
|
22
|
+
|
|
23
|
+
function resourceIsDeleted(proto: Resource): boolean {
|
|
24
|
+
return proto.deletedTime !== undefined && proto.deletedTime.seconds !== 0n;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** Throws "native" pl not found error, if resource is marked as deleted. */
|
|
28
|
+
export function protoToResource(proto: Resource): ResourceData {
|
|
29
|
+
if (resourceIsDeleted(proto)) throwPlNotFoundError('resource deleted');
|
|
30
|
+
return {
|
|
31
|
+
id: proto.id as ResourceId,
|
|
32
|
+
originalResourceId: proto.originalResourceId as OptionalResourceId,
|
|
33
|
+
type: notEmpty(proto.type),
|
|
34
|
+
data: proto.data,
|
|
35
|
+
inputsLocked: proto.inputsLocked,
|
|
36
|
+
outputsLocked: proto.outputsLocked,
|
|
37
|
+
resourceReady: proto.resourceReady,
|
|
38
|
+
kind: protoToResourceKind(proto.kind),
|
|
39
|
+
error: protoToError(proto),
|
|
40
|
+
final: proto.isFinal,
|
|
41
|
+
fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField)
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function protoToResourceKind(proto: Resource_Kind): ResourceKind {
|
|
46
|
+
switch (proto) {
|
|
47
|
+
case Resource_Kind.STRUCTURAL:
|
|
48
|
+
return 'Structural';
|
|
49
|
+
case Resource_Kind.VALUE:
|
|
50
|
+
return 'Value';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
throw new Error('invalid ResourceKind: ' + proto);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function protoToError(proto: Resource): OptionalResourceId {
|
|
57
|
+
const f = proto.fields.find((f) => f?.id?.fieldName === ResourceErrorField);
|
|
58
|
+
return (f?.error ?? NullResourceId) as OptionalResourceId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function protoToField(proto: Field): FieldData {
|
|
62
|
+
return {
|
|
63
|
+
name: notEmpty(proto.id?.fieldName),
|
|
64
|
+
type: protoToFieldType(proto.type),
|
|
65
|
+
status: protoToFieldStatus(proto.valueStatus),
|
|
66
|
+
value: proto.value as OptionalResourceId,
|
|
67
|
+
error: proto.error as OptionalResourceId,
|
|
68
|
+
valueIsFinal: proto.valueIsFinal
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function protoToFieldType(proto: GrpcFieldType): FieldType {
|
|
73
|
+
switch (proto) {
|
|
74
|
+
case GrpcFieldType.INPUT:
|
|
75
|
+
return 'Input';
|
|
76
|
+
case GrpcFieldType.OUTPUT:
|
|
77
|
+
return 'Output';
|
|
78
|
+
case GrpcFieldType.SERVICE:
|
|
79
|
+
return 'Service';
|
|
80
|
+
case GrpcFieldType.ONE_TIME_WRITABLE:
|
|
81
|
+
return 'OTW';
|
|
82
|
+
case GrpcFieldType.DYNAMIC:
|
|
83
|
+
return 'Dynamic';
|
|
84
|
+
case GrpcFieldType.MULTIPLE_TIMES_WRITABLE:
|
|
85
|
+
return 'MTW';
|
|
86
|
+
default:
|
|
87
|
+
throw new Error('invalid FieldType: ' + proto);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function protoToFieldStatus(proto: Field_ValueStatus): FieldStatus {
|
|
92
|
+
switch (proto) {
|
|
93
|
+
case Field_ValueStatus.EMPTY:
|
|
94
|
+
return 'Empty';
|
|
95
|
+
case Field_ValueStatus.ASSIGNED:
|
|
96
|
+
return 'Assigned';
|
|
97
|
+
case Field_ValueStatus.RESOLVED:
|
|
98
|
+
return 'Resolved';
|
|
99
|
+
default:
|
|
100
|
+
throw new Error('invalid FieldStatus: ' + proto);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export function fieldTypeToProto(type: FieldType): GrpcFieldType {
|
|
105
|
+
switch (type) {
|
|
106
|
+
case 'Input':
|
|
107
|
+
return GrpcFieldType.INPUT;
|
|
108
|
+
case 'Output':
|
|
109
|
+
return GrpcFieldType.OUTPUT;
|
|
110
|
+
case 'Dynamic':
|
|
111
|
+
return GrpcFieldType.DYNAMIC;
|
|
112
|
+
case 'Service':
|
|
113
|
+
return GrpcFieldType.SERVICE;
|
|
114
|
+
case 'MTW':
|
|
115
|
+
return GrpcFieldType.MULTIPLE_TIMES_WRITABLE;
|
|
116
|
+
case 'OTW':
|
|
117
|
+
return GrpcFieldType.ONE_TIME_WRITABLE;
|
|
118
|
+
default:
|
|
119
|
+
return assertNever(type);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createGlobalResourceId,
|
|
3
|
+
createLocalResourceId,
|
|
4
|
+
NullResourceId,
|
|
5
|
+
resourceIdFromString,
|
|
6
|
+
resourceIdToString
|
|
7
|
+
} from './types';
|
|
8
|
+
|
|
9
|
+
test.each(
|
|
10
|
+
[
|
|
11
|
+
NullResourceId,
|
|
12
|
+
createGlobalResourceId(true, 0x3457748574857n),
|
|
13
|
+
createGlobalResourceId(false, 0x3457748574857n),
|
|
14
|
+
createLocalResourceId(true, 1234, 34423),
|
|
15
|
+
createLocalResourceId(false, 1234, 34423)
|
|
16
|
+
].map((rid) => ({
|
|
17
|
+
name: resourceIdToString(rid),
|
|
18
|
+
rid
|
|
19
|
+
}))
|
|
20
|
+
)(`resource id to and from string: $name`, ({ rid }) => {
|
|
21
|
+
expect(resourceIdFromString(resourceIdToString(rid))).toEqual(rid);
|
|
22
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
2
|
+
import { version } from 'ts-jest/dist/transformers/hoist-jest';
|
|
3
|
+
|
|
4
|
+
// more details here: https://egghead.io/blog/using-branded-types-in-typescript
|
|
5
|
+
declare const __resource_id_type__: unique symbol;
|
|
6
|
+
type BrandResourceId<B> = bigint & { [__resource_id_type__]: B };
|
|
7
|
+
|
|
8
|
+
/** Global resource id */
|
|
9
|
+
export type ResourceId = BrandResourceId<'global'>;
|
|
10
|
+
|
|
11
|
+
/** Null resource id */
|
|
12
|
+
export type NullResourceId = BrandResourceId<'null'>;
|
|
13
|
+
|
|
14
|
+
/** Local resource id */
|
|
15
|
+
export type LocalResourceId = BrandResourceId<'local'>;
|
|
16
|
+
|
|
17
|
+
/** Any non-null resource id */
|
|
18
|
+
export type AnyResourceId = ResourceId | LocalResourceId;
|
|
19
|
+
|
|
20
|
+
/** Any resource id */
|
|
21
|
+
export type OptionalResourceId = NullResourceId | ResourceId;
|
|
22
|
+
|
|
23
|
+
/** All possible resource flavours */
|
|
24
|
+
export type OptionalAnyResourceId = NullResourceId | ResourceId | LocalResourceId;
|
|
25
|
+
|
|
26
|
+
export const NullResourceId = 0n as NullResourceId;
|
|
27
|
+
|
|
28
|
+
export function isNullResourceId(resourceId: bigint): resourceId is NullResourceId {
|
|
29
|
+
return resourceId === NullResourceId;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isNotNullResourceId(resourceId: OptionalResourceId): resourceId is ResourceId {
|
|
33
|
+
return resourceId !== NullResourceId;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function ensureResourceIdNotNull(resourceId: OptionalResourceId): ResourceId {
|
|
37
|
+
if (!isNotNullResourceId(resourceId)) throw new Error('null resource id');
|
|
38
|
+
return resourceId;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId {
|
|
42
|
+
return resourceId !== 0n;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// see local / global resource logic below...
|
|
46
|
+
|
|
47
|
+
export type ResourceKind = 'Structural' | 'Value';
|
|
48
|
+
|
|
49
|
+
export type FieldType = 'Input' | 'Output' | 'Service' | 'OTW' | 'Dynamic' | 'MTW';
|
|
50
|
+
|
|
51
|
+
export type FutureFieldType = 'Output' | 'Input' | 'Service';
|
|
52
|
+
|
|
53
|
+
export type FieldStatus = 'Empty' | 'Assigned' | 'Resolved';
|
|
54
|
+
|
|
55
|
+
export interface ResourceType {
|
|
56
|
+
readonly name: string;
|
|
57
|
+
readonly version: string;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function resourceType(name: string, version: string): ResourceType {
|
|
61
|
+
return { name, version };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function resourceTypeToString(rt: ResourceType): string {
|
|
65
|
+
return `${rt.name}:${rt.version}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function resourceTypesEqual(type1: ResourceType, type2: ResourceType): boolean {
|
|
69
|
+
return type1.name === type2.name && type1.version === type2.version;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Readonly fields here marks properties of resource that can't change according to pl's state machine. */
|
|
73
|
+
export interface BasicResourceData {
|
|
74
|
+
readonly id: ResourceId;
|
|
75
|
+
readonly originalResourceId: OptionalResourceId;
|
|
76
|
+
|
|
77
|
+
readonly kind: ResourceKind;
|
|
78
|
+
readonly type: ResourceType;
|
|
79
|
+
|
|
80
|
+
readonly data?: Uint8Array;
|
|
81
|
+
|
|
82
|
+
readonly error: OptionalResourceId;
|
|
83
|
+
|
|
84
|
+
readonly inputsLocked: boolean;
|
|
85
|
+
readonly outputsLocked: boolean;
|
|
86
|
+
readonly resourceReady: boolean;
|
|
87
|
+
|
|
88
|
+
/** This value is derived from resource state by the server and can be used as
|
|
89
|
+
* a robust criteria to determine resource is in final state. */
|
|
90
|
+
readonly final: boolean;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export const jsonToData = (data: unknown) => Buffer.from(JSON.stringify(data));
|
|
94
|
+
|
|
95
|
+
export const resDataToJson = (res: ResourceData) => JSON.parse(notEmpty(res.data).toString());
|
|
96
|
+
|
|
97
|
+
export interface ResourceData extends BasicResourceData {
|
|
98
|
+
fields: FieldData[];
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export function getField(r: ResourceData, name: string): FieldData {
|
|
102
|
+
return notEmpty(r.fields.find((f) => f.name === name));
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface FieldData {
|
|
106
|
+
name: string;
|
|
107
|
+
type: FieldType;
|
|
108
|
+
status: FieldStatus;
|
|
109
|
+
value: OptionalResourceId;
|
|
110
|
+
error: OptionalResourceId;
|
|
111
|
+
|
|
112
|
+
/** True if value the fields points to is in final state. */
|
|
113
|
+
valueIsFinal: boolean;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
//
|
|
117
|
+
// Local / Global ResourceId arithmetics
|
|
118
|
+
//
|
|
119
|
+
|
|
120
|
+
// Note: txId and other numerical values are made numbers but not bigint intentionally,
|
|
121
|
+
// after implementing security model based on signed resource ids this will make
|
|
122
|
+
// much more sense
|
|
123
|
+
|
|
124
|
+
const ResourceIdRootMask = 1n << 63n;
|
|
125
|
+
const ResourceIdLocalMask = 1n << 62n;
|
|
126
|
+
const NoFlagsIdMask = 0x3fffffffffffffffn;
|
|
127
|
+
const LocalResourceIdTxIdOffset = 24n;
|
|
128
|
+
export const MaxLocalId = 0xffffff;
|
|
129
|
+
export const MaxTxId = 0xffffffff;
|
|
130
|
+
/** Mask valid after applying shift */
|
|
131
|
+
const TxIdMask = BigInt(MaxTxId);
|
|
132
|
+
const LocalIdMask = BigInt(MaxLocalId);
|
|
133
|
+
|
|
134
|
+
// /** Basically removes embedded tx id */
|
|
135
|
+
// const LocalIdCleanMask = 0xFF00000000FFFFFFn;
|
|
136
|
+
|
|
137
|
+
export function isRootResourceId(id: bigint) {
|
|
138
|
+
return (id & ResourceIdRootMask) !== 0n;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function isLocalResourceId(id: bigint): id is LocalResourceId {
|
|
142
|
+
return (id & ResourceIdLocalMask) !== 0n;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function createLocalResourceId(
|
|
146
|
+
isRoot: boolean,
|
|
147
|
+
localCounterValue: number,
|
|
148
|
+
localTxId: number
|
|
149
|
+
): LocalResourceId {
|
|
150
|
+
if (
|
|
151
|
+
localCounterValue > MaxLocalId ||
|
|
152
|
+
localTxId > MaxTxId ||
|
|
153
|
+
localCounterValue < 0 ||
|
|
154
|
+
localTxId <= 0
|
|
155
|
+
)
|
|
156
|
+
throw Error('wrong local id or tx id');
|
|
157
|
+
return ((isRoot ? ResourceIdRootMask : 0n) |
|
|
158
|
+
ResourceIdLocalMask |
|
|
159
|
+
BigInt(localCounterValue) |
|
|
160
|
+
(BigInt(localTxId) << LocalResourceIdTxIdOffset)) as LocalResourceId;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): ResourceId {
|
|
164
|
+
return ((isRoot ? ResourceIdRootMask : 0n) | unmaskedId) as ResourceId;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function extractTxId(localResourceId: LocalResourceId): number {
|
|
168
|
+
return Number((localResourceId >> LocalResourceIdTxIdOffset) & TxIdMask);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void {
|
|
172
|
+
if (!isLocalResourceId(resourceId)) return;
|
|
173
|
+
if (extractTxId(resourceId) !== expectedTxId)
|
|
174
|
+
throw Error(
|
|
175
|
+
'local id from another transaction, globalize id before leaking it from the transaction'
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function resourceIdToString(resourceId: OptionalAnyResourceId): string {
|
|
180
|
+
if (isNullResourceId(resourceId)) return 'XX:0x0';
|
|
181
|
+
if (isLocalResourceId(resourceId))
|
|
182
|
+
return (
|
|
183
|
+
(isRootResourceId(resourceId) ? 'R' : 'N') +
|
|
184
|
+
'L:0x' +
|
|
185
|
+
(LocalIdMask & resourceId).toString(16) +
|
|
186
|
+
'[0x' +
|
|
187
|
+
extractTxId(resourceId).toString(16) +
|
|
188
|
+
']'
|
|
189
|
+
);
|
|
190
|
+
else
|
|
191
|
+
return (
|
|
192
|
+
(isRootResourceId(resourceId) ? 'R' : 'N') +
|
|
193
|
+
'G:0x' +
|
|
194
|
+
(NoFlagsIdMask & resourceId).toString(16)
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const resourceIdRegexp =
|
|
199
|
+
/^(?:(?<xx>XX)|(?<rn>[XRN])(?<lg>[XLG])):0x(?<rid>[0-9a-fA-F]+)(?:\[0x(?<txid>[0-9a-fA-F]+)])?$/;
|
|
200
|
+
|
|
201
|
+
export function resourceIdFromString(str: string): OptionalAnyResourceId | undefined {
|
|
202
|
+
const match = str.match(resourceIdRegexp);
|
|
203
|
+
if (match === null) return undefined;
|
|
204
|
+
const { xx, rn, lg, rid, txid } = match.groups!;
|
|
205
|
+
if (xx) return NullResourceId;
|
|
206
|
+
if (lg === 'L')
|
|
207
|
+
return createLocalResourceId(rn === 'R', Number.parseInt(rid, 16), Number.parseInt(txid, 16));
|
|
208
|
+
else return createGlobalResourceId(rn === 'R', BigInt('0x' + rid));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Converts bigint to global resource id */
|
|
212
|
+
export function bigintToResourceId(resourceId: bigint): ResourceId {
|
|
213
|
+
if (isLocalResourceId(resourceId))
|
|
214
|
+
throw new Error(`Local resource id: ${resourceIdToString(resourceId)}`);
|
|
215
|
+
if (isNullResourceId(resourceId)) throw new Error(`Null resource id.`);
|
|
216
|
+
return resourceId as ResourceId;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function stringifyWithResourceId(object: unknown | undefined): string {
|
|
220
|
+
return JSON.stringify(object, (key, value) =>
|
|
221
|
+
typeof value === 'bigint' ? resourceIdToString(value as OptionalAnyResourceId) : value
|
|
222
|
+
);
|
|
223
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { UnauthenticatedPlClient } from './unauth_client';
|
|
2
|
+
import { getTestConfig } from '../test/test_config';
|
|
3
|
+
import { UnauthenticatedError } from './errors';
|
|
4
|
+
|
|
5
|
+
test('ping test', async () => {
|
|
6
|
+
const client = new UnauthenticatedPlClient(getTestConfig().address);
|
|
7
|
+
const response = await client.ping();
|
|
8
|
+
expect(response).toHaveProperty('coreVersion');
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
test('wrong login', async () => {
|
|
12
|
+
const testConfig = getTestConfig();
|
|
13
|
+
if (testConfig.test_user === undefined || testConfig.test_password === undefined) {
|
|
14
|
+
console.log('skipped');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const client = new UnauthenticatedPlClient(testConfig.address);
|
|
18
|
+
await expect(client.login(testConfig.test_user, testConfig.test_password + 'A')).rejects.toThrow(
|
|
19
|
+
UnauthenticatedError
|
|
20
|
+
);
|
|
21
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { AuthInformation, PlClientConfig } from './config';
|
|
2
|
+
import {
|
|
3
|
+
AuthAPI_ListMethods_Response,
|
|
4
|
+
MaintenanceAPI_Ping_Response
|
|
5
|
+
} from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
6
|
+
import { LLPlClient } from './ll_client';
|
|
7
|
+
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
8
|
+
import { UnauthenticatedError } from './errors';
|
|
9
|
+
|
|
10
|
+
/** Primarily used for initial authentication (login) */
|
|
11
|
+
export class UnauthenticatedPlClient {
|
|
12
|
+
public readonly ll: LLPlClient;
|
|
13
|
+
|
|
14
|
+
constructor(configOrAddress: PlClientConfig | string) {
|
|
15
|
+
this.ll = new LLPlClient(configOrAddress);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
public async ping(): Promise<MaintenanceAPI_Ping_Response> {
|
|
19
|
+
return (await this.ll.grpcPl.ping({})).response;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
public async authMethods(): Promise<AuthAPI_ListMethods_Response> {
|
|
23
|
+
return (await this.ll.grpcPl.authMethods({})).response;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
public async requireAuth(): Promise<boolean> {
|
|
27
|
+
return (await this.authMethods()).methods.length > 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public async login(user: string, password: string): Promise<AuthInformation> {
|
|
31
|
+
try {
|
|
32
|
+
const response = await this.ll.grpcPl.getJWTToken(
|
|
33
|
+
{ expiration: { seconds: BigInt(this.ll.conf.authTTLSeconds), nanos: 0 } },
|
|
34
|
+
{
|
|
35
|
+
meta: {
|
|
36
|
+
authorization: 'Basic ' + Buffer.from(user + ':' + password).toString('base64')
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
).response;
|
|
40
|
+
const jwtToken = notEmpty(response.token);
|
|
41
|
+
if (jwtToken === '') throw new Error('empty token');
|
|
42
|
+
return { jwtToken };
|
|
43
|
+
} catch (e: any) {
|
|
44
|
+
if (e.code === 'UNAUTHENTICATED') throw new UnauthenticatedError(e.message);
|
|
45
|
+
throw new Error(e);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @packageDocumentation
|
|
3
|
+
*
|
|
4
|
+
* This file is exported under Pl "namespace" to the client users.
|
|
5
|
+
*
|
|
6
|
+
* It defines well known pl types, and methods to manipulate them.
|
|
7
|
+
*
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FutureFieldType, ResourceType } from '../core/types';
|
|
11
|
+
import { AnyRef, field, FieldRef, PlTransaction, ResourceRef } from '../core/transaction';
|
|
12
|
+
|
|
13
|
+
function rt(name: string, version: string): ResourceType {
|
|
14
|
+
return { name, version };
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const ClientRoot = rt('ClientRoot', '1');
|
|
18
|
+
|
|
19
|
+
export const StructTestResource = rt('StructTest', '1');
|
|
20
|
+
export const ValueTestResource = rt('ValueTest', '1');
|
|
21
|
+
|
|
22
|
+
export const JsonString = rt('json/string', '1');
|
|
23
|
+
export const JsonBool = rt('json/bool', '1');
|
|
24
|
+
export const JsonObject = rt('json/object', '1');
|
|
25
|
+
export const JsonArray = rt('json/array', '1');
|
|
26
|
+
export const JsonNumber = rt('json/number', '1');
|
|
27
|
+
export const JsonNull = rt('json/null', '1');
|
|
28
|
+
|
|
29
|
+
export const EphStdMap: ResourceType = rt('EphStdMap', '1');
|
|
30
|
+
export const StdMap: ResourceType = rt('StdMap', '1');
|
|
31
|
+
|
|
32
|
+
//
|
|
33
|
+
// Standard value resources
|
|
34
|
+
//
|
|
35
|
+
|
|
36
|
+
export function createPlNull(tx: PlTransaction): ResourceRef {
|
|
37
|
+
return tx.createValue(JsonNull, Buffer.from(JSON.stringify(null)));
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createPlBool(tx: PlTransaction, val: boolean): ResourceRef {
|
|
41
|
+
return tx.createValue(JsonBool, Buffer.from(JSON.stringify(val)));
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function createPlNumber(tx: PlTransaction, val: number): ResourceRef {
|
|
45
|
+
return tx.createValue(JsonNumber, Buffer.from(JSON.stringify(val)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function createPlString(tx: PlTransaction, val: string): ResourceRef {
|
|
49
|
+
return tx.createValue(JsonString, Buffer.from(JSON.stringify(val)));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function createPlArray(tx: PlTransaction, val: any[]): ResourceRef {
|
|
53
|
+
return tx.createValue(JsonArray, Buffer.from(JSON.stringify(val)));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function createPlObject(tx: PlTransaction, val: object): ResourceRef {
|
|
57
|
+
return tx.createValue(JsonObject, Buffer.from(JSON.stringify(val)));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
//
|
|
61
|
+
// Pl Map
|
|
62
|
+
//
|
|
63
|
+
|
|
64
|
+
export type PlRecordEntry<Key extends string = string, Ref extends AnyRef = AnyRef> = [Key, Ref];
|
|
65
|
+
|
|
66
|
+
export type PlRecord<Key extends string = string, Ref extends AnyRef = AnyRef> = Record<Key, Ref>;
|
|
67
|
+
|
|
68
|
+
export function plEntry<Key extends string = string, Ref extends AnyRef = AnyRef>(
|
|
69
|
+
key: Key,
|
|
70
|
+
ref: Ref
|
|
71
|
+
): PlRecordEntry<Key, Ref> {
|
|
72
|
+
return [key, ref];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export function plEntries<Key extends string = string, Ref extends AnyRef = AnyRef>(
|
|
76
|
+
record: PlRecord<Key, Ref>,
|
|
77
|
+
fields?: Key[]
|
|
78
|
+
): PlRecordEntry<Key, Ref>[] {
|
|
79
|
+
return fields === undefined
|
|
80
|
+
? (Object.entries(record) as PlRecordEntry<Key, Ref>[])
|
|
81
|
+
: fields.map((key) => plEntry(key, record[key]));
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Helper method to build standard pl map from a set of entries */
|
|
85
|
+
export function createPlMap(
|
|
86
|
+
tx: PlTransaction,
|
|
87
|
+
entries: PlRecordEntry[] | PlRecord,
|
|
88
|
+
ephemeral: boolean,
|
|
89
|
+
type?: ResourceType
|
|
90
|
+
): ResourceRef {
|
|
91
|
+
const actualType = type ?? (ephemeral ? EphStdMap : StdMap);
|
|
92
|
+
const rId = ephemeral ? tx.createEphemeral(actualType) : tx.createStruct(actualType);
|
|
93
|
+
|
|
94
|
+
for (const [name, value] of Array.isArray(entries) ? entries : plEntries(entries))
|
|
95
|
+
tx.createField(field(rId, name), 'Input', value);
|
|
96
|
+
|
|
97
|
+
tx.lock(rId);
|
|
98
|
+
|
|
99
|
+
return rId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function futureRecord<Key extends string>(
|
|
103
|
+
tx: PlTransaction,
|
|
104
|
+
rId: AnyRef,
|
|
105
|
+
keys: Key[],
|
|
106
|
+
fieldType: FutureFieldType,
|
|
107
|
+
prefix: string = ''
|
|
108
|
+
): PlRecord<Key, FieldRef> {
|
|
109
|
+
return Object.fromEntries(
|
|
110
|
+
keys.map((k) => plEntry(k, tx.getFutureFieldValue(rId, `${prefix}${k}`, fieldType)))
|
|
111
|
+
) as PlRecord<Key, FieldRef>;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
//
|
|
115
|
+
// Holder
|
|
116
|
+
//
|
|
117
|
+
|
|
118
|
+
/** Name of the field in block holder, that references the actual block-pack. */
|
|
119
|
+
export const Holder = StdMap;
|
|
120
|
+
export const EphHolder = EphStdMap;
|
|
121
|
+
export const HolderRefField = 'ref';
|
|
122
|
+
|
|
123
|
+
export function wrapInHolder(tx: PlTransaction, ref: AnyRef): ResourceRef {
|
|
124
|
+
const holder = tx.createStruct(Holder);
|
|
125
|
+
const mainHolderField = field(holder, HolderRefField);
|
|
126
|
+
tx.createField(mainHolderField, 'Input', ref);
|
|
127
|
+
tx.lock(holder);
|
|
128
|
+
return holder;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function wrapInEphHolder(tx: PlTransaction, ref: AnyRef): ResourceRef {
|
|
132
|
+
const holder = tx.createEphemeral(EphHolder);
|
|
133
|
+
const mainHolderField = field(holder, HolderRefField);
|
|
134
|
+
tx.createField(mainHolderField, 'Input', ref);
|
|
135
|
+
tx.lock(holder);
|
|
136
|
+
return holder;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function unwrapHolder(tx: PlTransaction, ref: AnyRef): FieldRef {
|
|
140
|
+
return tx.getFutureFieldValue(ref, HolderRefField, 'Input');
|
|
141
|
+
}
|