@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,730 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AnyResourceId,
|
|
3
|
+
createLocalResourceId,
|
|
4
|
+
ensureResourceIdNotNull,
|
|
5
|
+
LocalResourceId,
|
|
6
|
+
MaxTxId,
|
|
7
|
+
OptionalResourceId,
|
|
8
|
+
BasicResourceData,
|
|
9
|
+
FieldData,
|
|
10
|
+
FieldType,
|
|
11
|
+
ResourceData,
|
|
12
|
+
ResourceId,
|
|
13
|
+
ResourceType,
|
|
14
|
+
FutureFieldType
|
|
15
|
+
} from './types';
|
|
16
|
+
import {
|
|
17
|
+
ClientMessageRequest,
|
|
18
|
+
LLPlTransaction,
|
|
19
|
+
OneOfKind,
|
|
20
|
+
ServerMessageResponse
|
|
21
|
+
} from './ll_transaction';
|
|
22
|
+
import { TxAPI_Open_Request_WritableTx } from '../proto/github.com/milaboratory/pl/plapi/plapiproto/api';
|
|
23
|
+
import { NonUndefined } from 'utility-types';
|
|
24
|
+
import { toBytes } from '../util/util';
|
|
25
|
+
import { fieldTypeToProto, protoToField, protoToResource } from './type_conversion';
|
|
26
|
+
import { notEmpty } from '@milaboratories/ts-helpers';
|
|
27
|
+
import { isNotFoundError } from './errors';
|
|
28
|
+
|
|
29
|
+
/** Reference to resource, used only within transaction */
|
|
30
|
+
export interface ResourceRef {
|
|
31
|
+
/** Global resource id of newly created resources, become available only
|
|
32
|
+
* after response for the corresponding creation request is received. */
|
|
33
|
+
readonly globalId: Promise<ResourceId>;
|
|
34
|
+
|
|
35
|
+
/** Transaction-local resource id is assigned right after resource creation
|
|
36
|
+
* request is sent, and can be used right away */
|
|
37
|
+
readonly localId: LocalResourceId;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Key-Value pair from resource-attached KV storage */
|
|
41
|
+
export interface KeyValue {
|
|
42
|
+
key: string;
|
|
43
|
+
value: Uint8Array;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Key-Value pair from resource-attached KV storage */
|
|
47
|
+
export interface KeyValueString {
|
|
48
|
+
key: string;
|
|
49
|
+
value: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface _FieldId<RId> {
|
|
53
|
+
/** Parent resource id */
|
|
54
|
+
resourceId: RId;
|
|
55
|
+
/** Field name */
|
|
56
|
+
fieldName: string;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export type FieldId = _FieldId<ResourceId>;
|
|
60
|
+
export type FieldRef = _FieldId<ResourceRef>;
|
|
61
|
+
export type LocalFieldId = _FieldId<LocalResourceId>;
|
|
62
|
+
export type AnyFieldId = FieldId | LocalFieldId;
|
|
63
|
+
|
|
64
|
+
export type AnyResourceRef = ResourceRef | ResourceId;
|
|
65
|
+
export type AnyFieldRef = _FieldId<AnyResourceRef>; // FieldRef | FieldId
|
|
66
|
+
export type AnyRef = AnyResourceRef | AnyFieldRef;
|
|
67
|
+
|
|
68
|
+
export function isField(ref: AnyRef): ref is AnyFieldRef {
|
|
69
|
+
return ref.hasOwnProperty('resourceId') && ref.hasOwnProperty('fieldName');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function isResource(ref: AnyRef): ref is AnyResourceRef {
|
|
73
|
+
return (
|
|
74
|
+
typeof ref === 'bigint' || (ref.hasOwnProperty('globalId') && ref.hasOwnProperty('localId'))
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isFieldRef(ref: AnyFieldRef): ref is FieldRef {
|
|
79
|
+
return isResourceRef(ref.resourceId);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function isResourceRef(ref: AnyResourceRef): ref is ResourceRef {
|
|
83
|
+
return ref.hasOwnProperty('globalId') && ref.hasOwnProperty('localId');
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function toFieldId(ref: AnyFieldRef): AnyFieldId {
|
|
87
|
+
if (isFieldRef(ref)) return { resourceId: ref.resourceId.localId, fieldName: ref.fieldName };
|
|
88
|
+
else return ref as FieldId;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function toGlobalFieldId(ref: AnyFieldRef): Promise<FieldId> {
|
|
92
|
+
if (isFieldRef(ref))
|
|
93
|
+
return { resourceId: await ref.resourceId.globalId, fieldName: ref.fieldName };
|
|
94
|
+
else return ref as FieldId;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function toResourceId(ref: AnyResourceRef): AnyResourceId {
|
|
98
|
+
if (isResourceRef(ref)) return ref.localId;
|
|
99
|
+
else return ref;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export async function toGlobalResourceId(ref: AnyResourceRef): Promise<ResourceId> {
|
|
103
|
+
if (isResourceRef(ref)) return await ref.globalId;
|
|
104
|
+
else return ref;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export function field(resourceId: AnyResourceRef, fieldName: string): AnyFieldRef {
|
|
108
|
+
return { resourceId, fieldName };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** If transaction commit failed due to write conflicts */
|
|
112
|
+
export class TxCommitConflict extends Error {}
|
|
113
|
+
|
|
114
|
+
async function notFoundToUndefined<T>(cb: () => Promise<T>): Promise<T | undefined> {
|
|
115
|
+
try {
|
|
116
|
+
return await cb();
|
|
117
|
+
} catch (e: any) {
|
|
118
|
+
if (isNotFoundError(e)) return undefined;
|
|
119
|
+
throw e;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Each platform transaction has 3 stages:
|
|
125
|
+
* - initialization (txOpen message -> txInfo response)
|
|
126
|
+
* - communication (create resources, fields, references and so on)
|
|
127
|
+
* - finalization (txCommit or txDiscard message)
|
|
128
|
+
*
|
|
129
|
+
* This class encapsulates finalization stage and provides ready-to-communication transaction object.
|
|
130
|
+
* */
|
|
131
|
+
export class PlTransaction {
|
|
132
|
+
private readonly globalTxId: Promise<bigint>;
|
|
133
|
+
private readonly localTxId: number = PlTransaction.nextLocalTxId();
|
|
134
|
+
|
|
135
|
+
private localResourceIdCounter = 0;
|
|
136
|
+
|
|
137
|
+
/** Store logical tx open / closed state to prevent invalid sequence of requests.
|
|
138
|
+
* True means output stream was completed.
|
|
139
|
+
* Contract: there must be no async operations between setting this field to true and sending complete signal to stream.*/
|
|
140
|
+
private _completed = false;
|
|
141
|
+
|
|
142
|
+
/** Void operation futures are placed into this pool, and corresponding method return immediately.
|
|
143
|
+
* This is done to save number of round-trips. Next operation producing result will also await those
|
|
144
|
+
* pending ops, to throw any pending errors. */
|
|
145
|
+
private pendingVoidOps: Promise<void>[] = [];
|
|
146
|
+
|
|
147
|
+
constructor(
|
|
148
|
+
private readonly ll: LLPlTransaction,
|
|
149
|
+
public readonly name: string,
|
|
150
|
+
public readonly writable: boolean,
|
|
151
|
+
private readonly _clientRoot: OptionalResourceId
|
|
152
|
+
) {
|
|
153
|
+
// initiating transaction
|
|
154
|
+
this.globalTxId = this.sendSingleAndParse(
|
|
155
|
+
{
|
|
156
|
+
oneofKind: 'txOpen',
|
|
157
|
+
txOpen: {
|
|
158
|
+
name,
|
|
159
|
+
writable: writable
|
|
160
|
+
? TxAPI_Open_Request_WritableTx.WRITABLE
|
|
161
|
+
: TxAPI_Open_Request_WritableTx.NOT_WRITABLE
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
(r) => notEmpty(r.txOpen.tx?.id)
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
private async drainAndAwaitPendingOps(): Promise<void> {
|
|
169
|
+
if (this.pendingVoidOps.length === 0) return;
|
|
170
|
+
|
|
171
|
+
// drain pending operations into temp array
|
|
172
|
+
const pending = this.pendingVoidOps;
|
|
173
|
+
this.pendingVoidOps = [];
|
|
174
|
+
// awaiting these pending operations first, to catch any errors
|
|
175
|
+
await Promise.all(pending);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
private async sendSingleAndParse<Kind extends NonUndefined<ClientMessageRequest['oneofKind']>, T>(
|
|
179
|
+
r: OneOfKind<ClientMessageRequest, Kind>,
|
|
180
|
+
parser: (resp: OneOfKind<ServerMessageResponse, Kind>) => T
|
|
181
|
+
): Promise<T> {
|
|
182
|
+
// pushing operation packet to server
|
|
183
|
+
const rawResponsePromise = this.ll.send(r, false);
|
|
184
|
+
|
|
185
|
+
await this.drainAndAwaitPendingOps();
|
|
186
|
+
|
|
187
|
+
// awaiting our result, and parsing the response
|
|
188
|
+
return parser(await rawResponsePromise);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
private async sendMultiAndParse<Kind extends NonUndefined<ClientMessageRequest['oneofKind']>, T>(
|
|
192
|
+
r: OneOfKind<ClientMessageRequest, Kind>,
|
|
193
|
+
parser: (resp: OneOfKind<ServerMessageResponse, Kind>[]) => T
|
|
194
|
+
): Promise<T> {
|
|
195
|
+
// pushing operation packet to server
|
|
196
|
+
const rawResponsePromise = this.ll.send(r, true);
|
|
197
|
+
|
|
198
|
+
await this.drainAndAwaitPendingOps();
|
|
199
|
+
|
|
200
|
+
// awaiting our result, and parsing the response
|
|
201
|
+
return parser(await rawResponsePromise);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
private async sendVoidSync<Kind extends NonUndefined<ClientMessageRequest['oneofKind']>, T>(
|
|
205
|
+
r: OneOfKind<ClientMessageRequest, Kind>
|
|
206
|
+
): Promise<void> {
|
|
207
|
+
await this.ll.send(r, false);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/** Requests sent with this method should never produce recoverable errors */
|
|
211
|
+
private sendVoidAsync<Kind extends NonUndefined<ClientMessageRequest['oneofKind']>, T>(
|
|
212
|
+
r: OneOfKind<ClientMessageRequest, Kind>
|
|
213
|
+
): void {
|
|
214
|
+
this.pendingVoidOps.push(this.sendVoidSync(r));
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private checkTxOpen() {
|
|
218
|
+
if (this._completed) throw new Error('Transaction already closed');
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
public get completed() {
|
|
222
|
+
return this._completed;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Commit & closes transaction. {@link TxCommitConflict} is thrown on
|
|
226
|
+
* commit conflicts. */
|
|
227
|
+
public async commit() {
|
|
228
|
+
this.checkTxOpen();
|
|
229
|
+
|
|
230
|
+
// tx will accept no requests after this one
|
|
231
|
+
this._completed = true;
|
|
232
|
+
|
|
233
|
+
if (!this.writable) {
|
|
234
|
+
// no need to explicitly commit or reject read-only tx
|
|
235
|
+
const completeResult = this.ll.complete();
|
|
236
|
+
await this.drainAndAwaitPendingOps();
|
|
237
|
+
await completeResult;
|
|
238
|
+
await this.ll.await();
|
|
239
|
+
} else {
|
|
240
|
+
const commitResponse = this.sendSingleAndParse(
|
|
241
|
+
{ oneofKind: 'txCommit', txCommit: {} },
|
|
242
|
+
(r) => r.txCommit.success
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// send closing frame right after commit to save some time on round-trips
|
|
246
|
+
const completeResult = this.ll.complete();
|
|
247
|
+
|
|
248
|
+
// now when we pushed all packets into the stream, we should wait for any
|
|
249
|
+
// pending void operations from before, to catch any errors
|
|
250
|
+
await this.drainAndAwaitPendingOps();
|
|
251
|
+
|
|
252
|
+
if (!(await commitResponse)) throw new TxCommitConflict();
|
|
253
|
+
|
|
254
|
+
await completeResult;
|
|
255
|
+
|
|
256
|
+
// await event-loop completion
|
|
257
|
+
await this.ll.await();
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
public async discard() {
|
|
262
|
+
this.checkTxOpen();
|
|
263
|
+
|
|
264
|
+
// tx will accept no requests after this one
|
|
265
|
+
this._completed = true;
|
|
266
|
+
|
|
267
|
+
const discardResponse = this.sendVoidSync({ oneofKind: 'txDiscard', txDiscard: {} });
|
|
268
|
+
// send closing frame right after commit to save some time on round-trips
|
|
269
|
+
const completeResult = this.ll.complete();
|
|
270
|
+
|
|
271
|
+
// now when we pushed all packets into the stream, we should wait for any
|
|
272
|
+
// pending void operations from before, to catch any errors
|
|
273
|
+
await this.drainAndAwaitPendingOps();
|
|
274
|
+
|
|
275
|
+
await discardResponse;
|
|
276
|
+
await completeResult;
|
|
277
|
+
await this.ll.await();
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
//
|
|
281
|
+
// Main tx methods
|
|
282
|
+
//
|
|
283
|
+
|
|
284
|
+
public get clientRoot(): ResourceId {
|
|
285
|
+
return ensureResourceIdNotNull(this._clientRoot);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
//
|
|
289
|
+
// Resources
|
|
290
|
+
//
|
|
291
|
+
|
|
292
|
+
public createSingleton(
|
|
293
|
+
name: string,
|
|
294
|
+
type: ResourceType,
|
|
295
|
+
errorIfExists: boolean = false
|
|
296
|
+
): ResourceRef {
|
|
297
|
+
const localId = this.nextLocalResourceId(false);
|
|
298
|
+
|
|
299
|
+
const globalId = this.sendSingleAndParse(
|
|
300
|
+
{
|
|
301
|
+
oneofKind: 'resourceCreateSingleton',
|
|
302
|
+
resourceCreateSingleton: {
|
|
303
|
+
type,
|
|
304
|
+
id: localId,
|
|
305
|
+
data: Buffer.from(name),
|
|
306
|
+
errorIfExists
|
|
307
|
+
}
|
|
308
|
+
},
|
|
309
|
+
(r) => r.resourceCreateSingleton.resourceId as ResourceId
|
|
310
|
+
);
|
|
311
|
+
|
|
312
|
+
return { globalId, localId };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
public async getSingleton(name: string, loadFields: true): Promise<ResourceData>;
|
|
316
|
+
public async getSingleton(name: string, loadFields: false): Promise<BasicResourceData>;
|
|
317
|
+
public async getSingleton(
|
|
318
|
+
name: string,
|
|
319
|
+
loadFields: boolean = true
|
|
320
|
+
): Promise<BasicResourceData | ResourceData> {
|
|
321
|
+
return await this.sendSingleAndParse(
|
|
322
|
+
{
|
|
323
|
+
oneofKind: 'resourceGetSingleton',
|
|
324
|
+
resourceGetSingleton: {
|
|
325
|
+
data: Buffer.from(name),
|
|
326
|
+
loadFields
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
(r) => protoToResource(notEmpty(r.resourceGetSingleton.resource))
|
|
330
|
+
);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
private createResource<Kind extends NonUndefined<ClientMessageRequest['oneofKind']>>(
|
|
334
|
+
root: boolean,
|
|
335
|
+
req: (localId: LocalResourceId) => OneOfKind<ClientMessageRequest, Kind>,
|
|
336
|
+
parser: (resp: OneOfKind<ServerMessageResponse, Kind>) => bigint
|
|
337
|
+
): ResourceRef {
|
|
338
|
+
const localId = this.nextLocalResourceId(root);
|
|
339
|
+
|
|
340
|
+
const globalId = this.sendSingleAndParse(req(localId), (r) => parser(r) as ResourceId);
|
|
341
|
+
|
|
342
|
+
return { globalId, localId };
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
public createRoot(type: ResourceType): ResourceRef {
|
|
346
|
+
return this.createResource(
|
|
347
|
+
true,
|
|
348
|
+
(localId) => ({ oneofKind: 'resourceCreateRoot', resourceCreateRoot: { type, id: localId } }),
|
|
349
|
+
(r) => r.resourceCreateRoot.resourceId
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
public createStruct(type: ResourceType, data?: Uint8Array | string): ResourceRef {
|
|
354
|
+
return this.createResource(
|
|
355
|
+
false,
|
|
356
|
+
(localId) => ({
|
|
357
|
+
oneofKind: 'resourceCreateStruct',
|
|
358
|
+
resourceCreateStruct: {
|
|
359
|
+
type,
|
|
360
|
+
id: localId,
|
|
361
|
+
data: data === undefined ? undefined : typeof data === 'string' ? Buffer.from(data) : data
|
|
362
|
+
}
|
|
363
|
+
}),
|
|
364
|
+
(r) => r.resourceCreateStruct.resourceId
|
|
365
|
+
);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
public createEphemeral(type: ResourceType, data?: Uint8Array | string): ResourceRef {
|
|
369
|
+
return this.createResource(
|
|
370
|
+
false,
|
|
371
|
+
(localId) => ({
|
|
372
|
+
oneofKind: 'resourceCreateEphemeral',
|
|
373
|
+
resourceCreateEphemeral: {
|
|
374
|
+
type,
|
|
375
|
+
id: localId,
|
|
376
|
+
data: data === undefined ? undefined : typeof data === 'string' ? Buffer.from(data) : data
|
|
377
|
+
}
|
|
378
|
+
}),
|
|
379
|
+
(r) => r.resourceCreateEphemeral.resourceId
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
public createValue(
|
|
384
|
+
type: ResourceType,
|
|
385
|
+
data: Uint8Array | string,
|
|
386
|
+
errorIfExists: boolean = false
|
|
387
|
+
): ResourceRef {
|
|
388
|
+
return this.createResource(
|
|
389
|
+
false,
|
|
390
|
+
(localId) => ({
|
|
391
|
+
oneofKind: 'resourceCreateValue',
|
|
392
|
+
resourceCreateValue: {
|
|
393
|
+
type,
|
|
394
|
+
id: localId,
|
|
395
|
+
data: typeof data === 'string' ? Buffer.from(data) : data,
|
|
396
|
+
errorIfExists
|
|
397
|
+
}
|
|
398
|
+
}),
|
|
399
|
+
(r) => r.resourceCreateValue.resourceId
|
|
400
|
+
);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
public setResourceName(name: string, rId: AnyResourceRef): void {
|
|
404
|
+
this.sendVoidAsync({
|
|
405
|
+
oneofKind: 'resourceNameSet',
|
|
406
|
+
resourceNameSet: { resourceId: toResourceId(rId), name }
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
public deleteResourceName(name: string): void {
|
|
411
|
+
this.sendVoidAsync({ oneofKind: 'resourceNameDelete', resourceNameDelete: { name } });
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
public async getResourceByName(name: string): Promise<ResourceId> {
|
|
415
|
+
return await this.sendSingleAndParse(
|
|
416
|
+
{ oneofKind: 'resourceNameGet', resourceNameGet: { name } },
|
|
417
|
+
(r) => ensureResourceIdNotNull(r.resourceNameGet.resourceId as OptionalResourceId)
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
public async checkResourceNameExists(name: string): Promise<boolean> {
|
|
422
|
+
return await this.sendSingleAndParse(
|
|
423
|
+
{ oneofKind: 'resourceNameExists', resourceNameExists: { name } },
|
|
424
|
+
(r) => r.resourceNameExists.exists
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
public removeResource(rId: ResourceId): void {
|
|
429
|
+
this.sendVoidAsync({ oneofKind: 'resourceRemove', resourceRemove: { id: rId } });
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
public async resourceExists(rId: ResourceId): Promise<boolean> {
|
|
433
|
+
return await this.sendSingleAndParse(
|
|
434
|
+
{ oneofKind: 'resourceExists', resourceExists: { resourceId: rId } },
|
|
435
|
+
(r) => r.resourceExists.exists
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
public async getResourceData(rId: AnyResourceRef, loadFields: true): Promise<ResourceData>;
|
|
440
|
+
public async getResourceData(rId: AnyResourceRef, loadFields: false): Promise<BasicResourceData>;
|
|
441
|
+
public async getResourceData(
|
|
442
|
+
rId: AnyResourceRef,
|
|
443
|
+
loadFields: boolean
|
|
444
|
+
): Promise<BasicResourceData | ResourceData>;
|
|
445
|
+
public async getResourceData(
|
|
446
|
+
rId: AnyResourceRef,
|
|
447
|
+
loadFields: boolean = true
|
|
448
|
+
): Promise<BasicResourceData | ResourceData> {
|
|
449
|
+
return await this.sendSingleAndParse(
|
|
450
|
+
{
|
|
451
|
+
oneofKind: 'resourceGet',
|
|
452
|
+
resourceGet: { resourceId: toResourceId(rId), loadFields: loadFields }
|
|
453
|
+
},
|
|
454
|
+
(r) => protoToResource(notEmpty(r.resourceGet.resource))
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
public async getResourceDataIfExists(
|
|
459
|
+
rId: AnyResourceRef,
|
|
460
|
+
loadFields: true
|
|
461
|
+
): Promise<ResourceData | undefined>;
|
|
462
|
+
public async getResourceDataIfExists(
|
|
463
|
+
rId: AnyResourceRef,
|
|
464
|
+
loadFields: false
|
|
465
|
+
): Promise<BasicResourceData | undefined>;
|
|
466
|
+
public async getResourceDataIfExists(
|
|
467
|
+
rId: AnyResourceRef,
|
|
468
|
+
loadFields: boolean
|
|
469
|
+
): Promise<BasicResourceData | ResourceData | undefined>;
|
|
470
|
+
public async getResourceDataIfExists(
|
|
471
|
+
rId: AnyResourceRef,
|
|
472
|
+
loadFields: boolean = true
|
|
473
|
+
): Promise<BasicResourceData | ResourceData | undefined> {
|
|
474
|
+
return notFoundToUndefined(async () => await this.getResourceData(rId, loadFields));
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Inform platform that resource will not get any new input fields.
|
|
479
|
+
* This is required, when client creates resource without schema and wants
|
|
480
|
+
* controller to start calculations.
|
|
481
|
+
* Most controllers will not start calculations even when all inputs
|
|
482
|
+
* have their values, if inputs list is not locked.
|
|
483
|
+
*/
|
|
484
|
+
public lockInputs(rId: AnyResourceRef): void {
|
|
485
|
+
this.sendVoidAsync({
|
|
486
|
+
oneofKind: 'resourceLockInputs',
|
|
487
|
+
resourceLockInputs: { resourceId: toResourceId(rId) }
|
|
488
|
+
});
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Inform platform that resource will not get any new output fields.
|
|
493
|
+
* This is required for resource to pass deduplication.
|
|
494
|
+
*/
|
|
495
|
+
public lockOutputs(rId: AnyResourceRef): void {
|
|
496
|
+
this.sendVoidAsync({
|
|
497
|
+
oneofKind: 'resourceLockOutputs',
|
|
498
|
+
resourceLockOutputs: { resourceId: toResourceId(rId) }
|
|
499
|
+
});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
public lock(rID: AnyResourceRef): void {
|
|
503
|
+
this.lockInputs(rID);
|
|
504
|
+
this.lockOutputs(rID);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
//
|
|
508
|
+
// Fields
|
|
509
|
+
//
|
|
510
|
+
|
|
511
|
+
public createField(fId: AnyFieldRef, fieldType: FieldType, value?: AnyRef): void {
|
|
512
|
+
this.sendVoidAsync({
|
|
513
|
+
oneofKind: 'fieldCreate',
|
|
514
|
+
fieldCreate: { type: fieldTypeToProto(fieldType), id: toFieldId(fId) }
|
|
515
|
+
});
|
|
516
|
+
if (value !== undefined) this.setField(fId, value);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
public async fieldExists(fId: AnyFieldRef): Promise<boolean> {
|
|
520
|
+
return await this.sendSingleAndParse(
|
|
521
|
+
{
|
|
522
|
+
oneofKind: 'fieldExists',
|
|
523
|
+
fieldExists: { field: toFieldId(fId) }
|
|
524
|
+
},
|
|
525
|
+
(r) => r.fieldExists.exists
|
|
526
|
+
);
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
public setField(fId: AnyFieldRef, ref: AnyRef): void {
|
|
530
|
+
if (isResource(ref))
|
|
531
|
+
this.sendVoidAsync({
|
|
532
|
+
oneofKind: 'fieldSet',
|
|
533
|
+
fieldSet: {
|
|
534
|
+
field: toFieldId(fId),
|
|
535
|
+
value: {
|
|
536
|
+
resourceId: toResourceId(ref),
|
|
537
|
+
fieldName: '' // default value, read as undefined
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
else
|
|
542
|
+
this.sendVoidAsync({
|
|
543
|
+
oneofKind: 'fieldSet',
|
|
544
|
+
fieldSet: {
|
|
545
|
+
field: toFieldId(fId),
|
|
546
|
+
value: toFieldId(ref)
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
public setFieldError(fId: AnyFieldRef, ref: AnyResourceRef): void {
|
|
552
|
+
this.sendVoidAsync({
|
|
553
|
+
oneofKind: 'fieldSetError',
|
|
554
|
+
fieldSetError: { field: toFieldId(fId), errResourceId: toResourceId(ref) }
|
|
555
|
+
});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
public async getField(fId: AnyFieldRef): Promise<FieldData> {
|
|
559
|
+
return await this.sendSingleAndParse(
|
|
560
|
+
{ oneofKind: 'fieldGet', fieldGet: { field: toFieldId(fId) } },
|
|
561
|
+
(r) => protoToField(notEmpty(r.fieldGet.field))
|
|
562
|
+
);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
public async getFieldIfExists(fId: AnyFieldRef): Promise<FieldData | undefined> {
|
|
566
|
+
return notFoundToUndefined(async () => await this.getField(fId));
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
public resetField(fId: AnyFieldRef): void {
|
|
570
|
+
this.sendVoidAsync({ oneofKind: 'fieldReset', fieldReset: { field: toFieldId(fId) } });
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
public removeField(fId: AnyFieldRef): void {
|
|
574
|
+
this.sendVoidAsync({ oneofKind: 'fieldRemove', fieldRemove: { field: toFieldId(fId) } });
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
//
|
|
578
|
+
// KV
|
|
579
|
+
//
|
|
580
|
+
|
|
581
|
+
public async listKeyValues(rId: AnyResourceRef): Promise<KeyValue[]> {
|
|
582
|
+
return await this.sendMultiAndParse(
|
|
583
|
+
{
|
|
584
|
+
oneofKind: 'resourceKeyValueList',
|
|
585
|
+
resourceKeyValueList: { resourceId: toResourceId(rId), startFrom: '', limit: 0 }
|
|
586
|
+
},
|
|
587
|
+
(r) => r.map((e) => e.resourceKeyValueList.record!)
|
|
588
|
+
);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
public async listKeyValuesString(rId: AnyResourceRef): Promise<KeyValueString[]> {
|
|
592
|
+
return (await this.listKeyValues(rId)).map(({ key, value }) => ({
|
|
593
|
+
key,
|
|
594
|
+
value: Buffer.from(value).toString()
|
|
595
|
+
}));
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
public async listKeyValuesIfResourceExists(rId: AnyResourceRef): Promise<KeyValue[] | undefined> {
|
|
599
|
+
return notFoundToUndefined(async () => await this.listKeyValues(rId));
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
public async listKeyValuesStringIfResourceExists(
|
|
603
|
+
rId: AnyResourceRef
|
|
604
|
+
): Promise<KeyValueString[] | undefined> {
|
|
605
|
+
return notFoundToUndefined(async () => await this.listKeyValuesString(rId));
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
public setKValue(rId: AnyResourceRef, key: string, value: Uint8Array | string): void {
|
|
609
|
+
this.sendVoidAsync({
|
|
610
|
+
oneofKind: 'resourceKeyValueSet',
|
|
611
|
+
resourceKeyValueSet: {
|
|
612
|
+
resourceId: toResourceId(rId),
|
|
613
|
+
key,
|
|
614
|
+
value: toBytes(value)
|
|
615
|
+
}
|
|
616
|
+
});
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
public deleteKValue(rId: AnyResourceRef, key: string): void {
|
|
620
|
+
this.sendVoidAsync({
|
|
621
|
+
oneofKind: 'resourceKeyValueDelete',
|
|
622
|
+
resourceKeyValueDelete: {
|
|
623
|
+
resourceId: toResourceId(rId),
|
|
624
|
+
key
|
|
625
|
+
}
|
|
626
|
+
});
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
public async getKValue(rId: AnyResourceRef, key: string): Promise<Uint8Array> {
|
|
630
|
+
return await this.sendSingleAndParse(
|
|
631
|
+
{
|
|
632
|
+
oneofKind: 'resourceKeyValueGet',
|
|
633
|
+
resourceKeyValueGet: { resourceId: toResourceId(rId), key }
|
|
634
|
+
},
|
|
635
|
+
(r) => r.resourceKeyValueGet.value
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
public async getKValueString(rId: AnyResourceRef, key: string): Promise<string> {
|
|
640
|
+
return Buffer.from(await this.getKValue(rId, key)).toString();
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
public async getKValueJson<T>(rId: AnyResourceRef, key: string): Promise<T> {
|
|
644
|
+
return JSON.parse(await this.getKValueString(rId, key)) as T;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
public async getKValueIfExists(
|
|
648
|
+
rId: AnyResourceRef,
|
|
649
|
+
key: string
|
|
650
|
+
): Promise<Uint8Array | undefined> {
|
|
651
|
+
return await this.sendSingleAndParse(
|
|
652
|
+
{
|
|
653
|
+
oneofKind: 'resourceKeyValueGetIfExists',
|
|
654
|
+
resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), key }
|
|
655
|
+
},
|
|
656
|
+
(r) =>
|
|
657
|
+
r.resourceKeyValueGetIfExists.exists ? r.resourceKeyValueGetIfExists.value : undefined
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
public async getKValueStringIfExists(
|
|
662
|
+
rId: AnyResourceRef,
|
|
663
|
+
key: string
|
|
664
|
+
): Promise<string | undefined> {
|
|
665
|
+
const data = await this.getKValueIfExists(rId, key);
|
|
666
|
+
return data === undefined ? undefined : Buffer.from(data).toString();
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
public async getKValueJsonIfExists<T>(rId: AnyResourceRef, key: string): Promise<T | undefined> {
|
|
670
|
+
const str = await this.getKValueString(rId, key);
|
|
671
|
+
if (str === undefined) return undefined;
|
|
672
|
+
return JSON.parse(str) as T;
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
//
|
|
676
|
+
// Cache
|
|
677
|
+
//
|
|
678
|
+
// TODO
|
|
679
|
+
|
|
680
|
+
//
|
|
681
|
+
// High level ops
|
|
682
|
+
//
|
|
683
|
+
|
|
684
|
+
/** Resolves existing or create first level resource from */
|
|
685
|
+
public getFutureFieldValue(rId: AnyRef, fieldName: string, fieldType: FutureFieldType): FieldRef {
|
|
686
|
+
const data = Buffer.from(JSON.stringify({ fieldName, fieldType }));
|
|
687
|
+
const getFieldResource = this.createEphemeral({ name: 'json/getField', version: '1' }, data);
|
|
688
|
+
this.setField({ resourceId: getFieldResource, fieldName: 'resource' }, rId);
|
|
689
|
+
return { resourceId: getFieldResource, fieldName: 'result' };
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
//
|
|
693
|
+
// Technical
|
|
694
|
+
//
|
|
695
|
+
|
|
696
|
+
public async getGlobalTxId() {
|
|
697
|
+
return await this.globalTxId;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
/** Closes output event stream */
|
|
701
|
+
public async complete() {
|
|
702
|
+
if (this._completed) return;
|
|
703
|
+
this._completed = true;
|
|
704
|
+
const completeResult = this.ll.complete();
|
|
705
|
+
await this.drainAndAwaitPendingOps();
|
|
706
|
+
await completeResult;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
/** Await incoming message loop termination and throw
|
|
710
|
+
* any leftover errors if it was unsuccessful */
|
|
711
|
+
public async await() {
|
|
712
|
+
await this.ll.await();
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
//
|
|
716
|
+
// Helpers
|
|
717
|
+
//
|
|
718
|
+
|
|
719
|
+
private nextLocalResourceId(root: boolean): LocalResourceId {
|
|
720
|
+
return createLocalResourceId(root, ++this.localResourceIdCounter, this.localTxId);
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
private static localTxIdCounter = 0;
|
|
724
|
+
|
|
725
|
+
private static nextLocalTxId() {
|
|
726
|
+
PlTransaction.localTxIdCounter++;
|
|
727
|
+
if (PlTransaction.localTxIdCounter === MaxTxId) PlTransaction.localTxIdCounter = 1;
|
|
728
|
+
return PlTransaction.localTxIdCounter;
|
|
729
|
+
}
|
|
730
|
+
}
|