From 2c8cdd439be5aeaccb08ed0adb13d282b85db4c9 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 13:26:18 +0100 Subject: [PATCH 01/27] MILAB-5815: feat: proto update --- .../proto/plapi/plapiproto/api.proto | 143 ++- .../proto/plapi/plapiproto/api_types.proto | 39 +- lib/node/pl-client/src/core/transaction.ts | 4 +- .../pl-client/src/core/type_conversion.ts | 2 +- .../pl/plapi/plapiproto/api.client.ts | 81 +- .../milaboratory/pl/plapi/plapiproto/api.ts | 1109 +++++++++++++++-- .../pl/plapi/plapiproto/api_types.ts | 283 ++++- .../proto-grpc/google/protobuf/descriptor.ts | 7 +- .../src/proto-grpc/google/protobuf/struct.ts | 2 +- 9 files changed, 1526 insertions(+), 144 deletions(-) diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api.proto b/lib/node/pl-client/proto/plapi/plapiproto/api.proto index 9477b5f8e7..b0d27c63d1 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api.proto @@ -8,6 +8,7 @@ import "github.com/milaboratory/pl/plapi/plapiproto/api_types.proto"; import "github.com/milaboratory/pl/plapi/plapiproto/base_types.proto"; import "github.com/milaboratory/pl/plapi/plapiproto/resource_types.proto"; import "google/protobuf/duration.proto"; +import "google/protobuf/timestamp.proto"; option go_package = "github.com/milaboratory/pl/plapi/plapiproto;plapiproto"; @@ -130,21 +131,46 @@ service Platform { // // Locks // + + // LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: + // - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + // - list resource's fields, take fields with names set in request + // - get resolved values of listed fields (IDs of 'ON' resources). + // - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. + // + // Lock logic constraints: + // - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them + // succeeds, while other fails with error (no long waiting) + // - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know + // resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. + // - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before + // being lockable. Attempt to lock resource that is not became original will fail. + // - Locking is one-way operation: it cannot be 'released' or 'revoked'. + rpc LockFieldValues(LocksAPI.LockFieldValues.Create.Request) returns (LocksAPI.LockFieldValues.Create.Response) { + option (google.api.http) = { + post: "/v1/locks/lock/create" + body: "*" + }; + } + + // LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. + // Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + // To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. rpc LeaseResource(LocksAPI.Lease.Create.Request) returns (LocksAPI.Lease.Create.Response) { option (google.api.http) = { - post: "/v1/locks/lease" + post: "/v1/locks/lease/create" body: "*" }; } rpc UpdateLease(LocksAPI.Lease.Update.Request) returns (LocksAPI.Lease.Update.Response) { option (google.api.http) = { - put: "/v1/locks/lease" + post: "/v1/locks/lease/update" body: "*" }; } rpc ReleaseLease(LocksAPI.Lease.Release.Request) returns (LocksAPI.Lease.Release.Response) { option (google.api.http) = { - delete: "/v1/locks/lease" + post: "/v1/locks/lease/release" body: "*" }; } @@ -385,10 +411,12 @@ message TxAPI { ControllerKVAPI.GetFlagIfExists.Request controller_key_value_get_flag_if_exists = 255; // get a bool from a controller's key-value store, return false if key is not found. CacheAPI.SetToField.Request cache_set_to_field = 300; // store the topology info and results of the field. - CacheAPI.DeleteExpiredRecords.Request cache_delete_expired_records = 301; // iterate through all records and delete expired. + Util.Deprecated cache_delete_expired_records = 301 [deprecated = true]; ControllerAPI.SetFeatures.Request controller_features_set = 350; // replace list of resource features, provided by controller. ControllerAPI.ClearFeatures.Request controller_features_clear = 351; // clear list of resource features, provided by controller. + + SetDefaultColor.Request set_default_color = 400; // set default color for resource creation } } @@ -482,10 +510,12 @@ message TxAPI { ControllerKVAPI.GetFlagIfExists.Response controller_key_value_get_flag_if_exists = 255; CacheAPI.SetToField.Response cache_set_to_field = 300; - CacheAPI.DeleteExpiredRecords.Response cache_delete_expired_records = 301; + Util.Deprecated cache_delete_expired_records = 301 [deprecated = true]; ControllerAPI.SetFeatures.Response controller_features_set = 350; ControllerAPI.ClearFeatures.Response controller_features_clear = 351; + + SetDefaultColor.Response set_default_color = 400; } google.rpc.Status error = 3; @@ -533,6 +563,13 @@ message TxAPI { message Response {} } + + message SetDefaultColor { + message Request { + bytes color_proof = 1; + } + message Response {} + } } message ResourceAPI { @@ -542,10 +579,12 @@ message ResourceAPI { Base.ResourceType type = 3; optional bytes data = 4; + bytes color_proof = 5; } message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } message CreateEphemeral { @@ -554,10 +593,12 @@ message ResourceAPI { Base.ResourceType type = 3; optional bytes data = 4; + bytes color_proof = 5; } message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -581,10 +622,12 @@ message ResourceAPI { bytes data = 6; bool error_if_exists = 7; + bytes color_proof = 8; } message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -596,6 +639,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -607,10 +651,13 @@ message ResourceAPI { bytes data = 6; bool error_if_exists = 7; + + bytes color_proof = 8; } message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -630,6 +677,7 @@ message ResourceAPI { message LockInputs { message Request { uint64 resource_id = 1; + bytes resource_signature = 2; } message Response {} @@ -638,6 +686,7 @@ message ResourceAPI { message LockOutputs { message Request { uint64 resource_id = 1; + bytes resource_signature = 2; } message Response {} @@ -646,6 +695,7 @@ message ResourceAPI { message Exists { message Request { uint64 resource_id = 1; + bytes resource_signature = 2; } message Response { @@ -656,7 +706,10 @@ message ResourceAPI { message SetError { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + uint64 error_resource_id = 2; + bytes error_resource_signature = 4; } message Response {} } @@ -664,6 +717,8 @@ message ResourceAPI { message Get { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + bool load_fields = 2; } @@ -708,6 +763,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -716,7 +772,8 @@ message ResourceAPI { // Remove any resource, that has garbage collection disabled message Remove { message Request { - uint64 id = 1; + uint64 resource_id = 1; + bytes resource_signature = 2; } message Response {} @@ -739,6 +796,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; + bytes resource_signature = 2; } } @@ -764,6 +822,7 @@ message ResourceAPI { message Tree { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; // Limit maximum depth the tree is traversed. // The resource is considered on depth = 0, the values of its fiels @@ -783,6 +842,7 @@ message ResourceAPI { message TreeSize { message Request { uint64 resource_id = 1; + bytes resource_signature = 2; } message Response { // size of all tree resources in bytes @@ -829,7 +889,8 @@ message FieldAPI { message SetError { message Request { Base.FieldRef field = 1; - uint64 err_resource_id = 2; + uint64 error_resource_id = 2; + bytes error_resource_signature = 3; } message Response {} @@ -862,6 +923,7 @@ message FieldAPI { message List { message Request { uint64 resource_id = 1; + bytes resource_signature = 4; // Start the listing from given position, returning first field with // name >= start_from. @@ -1115,6 +1177,7 @@ message ResourceKVAPI { message List { message Request { uint64 resource_id = 1; + bytes resource_signature = 4; // Start the listing from given position, returning first item with // key >= start_from. @@ -1148,6 +1211,8 @@ message ResourceKVAPI { message Set { message Request { uint64 resource_id = 1; + bytes resource_signature = 4; + string key = 2; bytes value = 3; } @@ -1158,6 +1223,8 @@ message ResourceKVAPI { message Get { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + string key = 2; } @@ -1169,6 +1236,8 @@ message ResourceKVAPI { message GetIfExists { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + string key = 2; } @@ -1181,6 +1250,8 @@ message ResourceKVAPI { message Delete { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + string key = 2; } @@ -1190,6 +1261,8 @@ message ResourceKVAPI { message SetFlag { message Request { uint64 resource_id = 1; + bytes resource_signature = 4; + string key = 2; bool value = 3; } @@ -1200,6 +1273,8 @@ message ResourceKVAPI { message GetFlag { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + string key = 2; } @@ -1286,17 +1361,45 @@ message CacheAPI { message Response {} } - message DeleteExpiredRecords { - message Request {} - message Response {} - } } message LocksAPI { + message LockFieldValues { + message Create { + message Request { + uint64 resource_id = 1; // resource ID that will own the lock + repeated string lock_references_of = 2; // list of fields, which values should be locked + string comment = 3; // comment to show for other lockers on lock conflict. Truncated to 1024 bytes. + } + + message Response { + // true when lock was acquired (new, or already owned by the owner) + // Client MUST pay attention to this flag, as it shows if lock was successfull. + bool acquired = 1; + + // Info about why lock was not acquired. + // Limited number of conflicts is reported: i.e. if lock operation failed for 20 fields, only first 10 are listed here. + // The number '10' is not a fixed contract for external clients. It is just 'somehow truncated'. + repeated LockInfo conflicting_locks = 2; + bool conflicts_list_truncated = 3; // true if there were more conflicts than reported + + message LockInfo { + uint64 target_id = 1; // ID of resource that failed to be locked + string field_name = 2; // what field was referencing this + + uint64 locked_by = 3; // Who is owning the lock for + google.protobuf.Timestamp locked_at = 4; // when the lock was obtained + string comment = 5; // comment from the locker holding the lock + } + } + } + } + message Lease { message Create { message Request { uint64 resource_id = 1; + bytes resource_signature = 5; google.protobuf.Duration timeout = 3; string name = 4; @@ -1309,8 +1412,9 @@ message LocksAPI { message Update { message Request { uint64 resource_id = 1; - bytes lease_id = 2; + bytes resource_signature = 5; + bytes lease_id = 2; google.protobuf.Duration timeout = 3; string name = 4; } @@ -1320,6 +1424,8 @@ message LocksAPI { message Release { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; + bytes lease_id = 2; } message Response {} @@ -1343,12 +1449,21 @@ message AuthAPI { } message GetJWTToken { + enum Role { + ROLE_UNSPECIFIED = 0; // issue JWT with caller's natural role + USER = 1; + CONTROLLER = 2; + WORKFLOW = 3; + } + message Request { google.protobuf.Duration expiration = 1; + optional Role requested_role = 2; } message Response { string token = 1; + bytes session_id = 2; } } } @@ -1404,3 +1519,7 @@ message MaintenanceAPI { } } } + +message Util { + message Deprecated {} +} diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto index bbc977d3ba..997ca75a7b 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto @@ -20,7 +20,8 @@ message Resource { KIND_VALUE = 2; } - uint64 id = 2; + uint64 resource_id = 2; + bytes resource_signature = 18; bytes canonical_id = 17; // could be empty, it depends on resource lifecycle state Kind kind = 3; Base.ResourceType type = 4; @@ -46,6 +47,8 @@ message Resource { google.protobuf.Timestamp created_time = 12; google.protobuf.Timestamp deleted_time = 13; + + // next free: 19 } message Field { @@ -60,6 +63,9 @@ message Field { // reference. At that moment all fields in the chain will get their values // resolved and will start to refer to the same resource directly. uint64 value = 5; + // Signature for value resource ID, inheriting the parent resource's color. + // Populated server-side when the parent resource has a known color in the current TX. + bytes value_signature = 10; enum ValueStatus { INVALID = 0; @@ -77,6 +83,8 @@ message Field { // Resource error resource id if any. // Is intended to report problems _from_ platform to client. uint64 error = 6; + // Signature for error resource ID, inheriting the parent resource's color. + bytes error_signature = 11; } message Notification { @@ -101,7 +109,9 @@ message Notification { bool generic_otw_set = 14; bool dynamic_changed = 10; - // next free: 17; + bool resource_recovered = 17; + + // next free: 18; } message FieldChange { @@ -144,6 +154,31 @@ message ResourceSchema { Base.ResourceType type = 1; repeated FieldSchema fields = 2; + + message AccessFlags { + // Deny-list approach: default = allowed (true) + // Controllers set these to false to restrict non-controller roles (role='u', role='w') + optional bool create_resource = 1; // default: true (creation allowed) + + // IMPORTANT: read_fields=false with write_fields=true is a forbidden combination + optional bool read_fields = 2; // default: true (reads allowed) + optional bool write_fields = 3; // default: true (writes allowed) + + // IMPORTANT: read_kv=false with write_kv=true is a forbidden combination + optional bool read_kv = 4; // if unset, falls back to read_fields + optional bool write_kv = 5; // if unset, falls back to write_fields + + // Per-field-type overrides (map: field_type → bool) + // When defined for a field type, overrides resource-level flags + map read_by_field_type = 6; + map write_by_field_type = 7; + } + + // Access restriction flags for non-controller roles + optional AccessFlags access_flags = 3; + + bool free_inputs = 4; // if true, skip automatic input locking on creation + bool free_outputs = 5; // if true, skip automatic output locking on creation } message FieldSchema { diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 0626d1cbf5..f3fe97ce6e 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -518,7 +518,7 @@ export class PlTransaction { } public removeResource(rId: ResourceId): void { - this.sendVoidAsync({ oneofKind: "resourceRemove", resourceRemove: { id: rId } }); + this.sendVoidAsync({ oneofKind: "resourceRemove", resourceRemove: { resourceId: rId } }); } public resourceExists(rId: ResourceId): Promise { @@ -743,7 +743,7 @@ export class PlTransaction { this._stat.fieldsSet++; this.sendVoidAsync({ oneofKind: "fieldSetError", - fieldSetError: { field: toFieldId(fId), errResourceId: toResourceId(ref) }, + fieldSetError: { field: toFieldId(fId), errorResourceId: toResourceId(ref) }, }); } diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index 46efbd72c4..f08b62eb19 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -30,7 +30,7 @@ function resourceIsDeleted(proto: Resource): boolean { export function protoToResource(proto: Resource): ResourceData { if (resourceIsDeleted(proto)) throwPlNotFoundError("resource deleted"); return { - id: proto.id as ResourceId, + id: proto.resourceId as ResourceId, originalResourceId: proto.originalResourceId as OptionalResourceId, type: notEmpty(proto.type), data: proto.data, diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts index afd5cdb1d6..13d72e9ca7 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts @@ -20,6 +20,8 @@ import type { LocksAPI_Lease_Update_Response } from "./api"; import type { LocksAPI_Lease_Update_Request } from "./api"; import type { LocksAPI_Lease_Create_Response } from "./api"; import type { LocksAPI_Lease_Create_Request } from "./api"; +import type { LocksAPI_LockFieldValues_Create_Response } from "./api"; +import type { LocksAPI_LockFieldValues_Create_Request } from "./api"; import type { ControllerAPI_ClearFeatures_Response } from "./api"; import type { ControllerAPI_ClearFeatures_Request } from "./api"; import type { ControllerAPI_SetFeatures_Response } from "./api"; @@ -148,10 +150,33 @@ export interface IPlatformClient { * @generated from protobuf rpc: ControllerClearFeatures */ controllerClearFeatures(input: ControllerAPI_ClearFeatures_Request, options?: RpcOptions): UnaryCall; - /** - * - * Locks - * + // + // Locks + // + + /** + * LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: + * - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + * - list resource's fields, take fields with names set in request + * - get resolved values of listed fields (IDs of 'ON' resources). + * - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. + * + * Lock logic constraints: + * - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them + * succeeds, while other fails with error (no long waiting) + * - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know + * resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. + * - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before + * being lockable. Attempt to lock resource that is not became original will fail. + * - Locking is one-way operation: it cannot be 'released' or 'revoked'. + * + * @generated from protobuf rpc: LockFieldValues + */ + lockFieldValues(input: LocksAPI_LockFieldValues_Create_Request, options?: RpcOptions): UnaryCall; + /** + * LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. + * Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + * To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. * * @generated from protobuf rpc: LeaseResource */ @@ -344,29 +369,55 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { const method = this.methods[17], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } + // + // Locks + // + + /** + * LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: + * - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + * - list resource's fields, take fields with names set in request + * - get resolved values of listed fields (IDs of 'ON' resources). + * - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. + * + * Lock logic constraints: + * - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them + * succeeds, while other fails with error (no long waiting) + * - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know + * resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. + * - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before + * being lockable. Attempt to lock resource that is not became original will fail. + * - Locking is one-way operation: it cannot be 'released' or 'revoked'. + * + * @generated from protobuf rpc: LockFieldValues + */ + lockFieldValues(input: LocksAPI_LockFieldValues_Create_Request, options?: RpcOptions): UnaryCall { + const method = this.methods[18], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } /** - * - * Locks - * + * LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. + * Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + * To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. * * @generated from protobuf rpc: LeaseResource */ leaseResource(input: LocksAPI_Lease_Create_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[18], opt = this._transport.mergeOptions(options); + const method = this.methods[19], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** * @generated from protobuf rpc: UpdateLease */ updateLease(input: LocksAPI_Lease_Update_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[19], opt = this._transport.mergeOptions(options); + const method = this.methods[20], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** * @generated from protobuf rpc: ReleaseLease */ releaseLease(input: LocksAPI_Lease_Release_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[20], opt = this._transport.mergeOptions(options); + const method = this.methods[21], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -377,14 +428,14 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { * @generated from protobuf rpc: AuthMethods */ authMethods(input: AuthAPI_ListMethods_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[21], opt = this._transport.mergeOptions(options); + const method = this.methods[22], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** * @generated from protobuf rpc: GetJWTToken */ getJWTToken(input: AuthAPI_GetJWTToken_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[22], opt = this._transport.mergeOptions(options); + const method = this.methods[23], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -395,7 +446,7 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { * @generated from protobuf rpc: ListResourceTypes */ listResourceTypes(input: MiscAPI_ListResourceTypes_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[23], opt = this._transport.mergeOptions(options); + const method = this.methods[24], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -406,14 +457,14 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { * @generated from protobuf rpc: Ping */ ping(input: MaintenanceAPI_Ping_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[24], opt = this._transport.mergeOptions(options); + const method = this.methods[25], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** * @generated from protobuf rpc: License */ license(input: MaintenanceAPI_License_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[25], opt = this._transport.mergeOptions(options); + const method = this.methods[26], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } } diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts index f089d94a34..3ced0e08e4 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts @@ -11,6 +11,7 @@ import { UnknownFieldHandler } from "@protobuf-ts/runtime"; import type { PartialMessage } from "@protobuf-ts/runtime"; import { reflectionMergePartial } from "@protobuf-ts/runtime"; import { MessageType } from "@protobuf-ts/runtime"; +import { Timestamp } from "../../../../../google/protobuf/timestamp"; import { Duration } from "../../../../../google/protobuf/duration"; import { ResourceAPIFeature } from "./api_types"; import { Controller } from "./api_types"; @@ -503,9 +504,10 @@ export interface TxAPI_ClientMessage { } | { oneofKind: "cacheDeleteExpiredRecords"; /** - * @generated from protobuf field: MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Request cache_delete_expired_records = 301 + * @deprecated + * @generated from protobuf field: MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true] */ - cacheDeleteExpiredRecords: CacheAPI_DeleteExpiredRecords_Request; // iterate through all records and delete expired. + cacheDeleteExpiredRecords: Util_Deprecated; } | { oneofKind: "controllerFeaturesSet"; /** @@ -518,6 +520,12 @@ export interface TxAPI_ClientMessage { * @generated from protobuf field: MiLaboratories.PL.API.ControllerAPI.ClearFeatures.Request controller_features_clear = 351 */ controllerFeaturesClear: ControllerAPI_ClearFeatures_Request; // clear list of resource features, provided by controller. + } | { + oneofKind: "setDefaultColor"; + /** + * @generated from protobuf field: MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request set_default_color = 400 + */ + setDefaultColor: TxAPI_SetDefaultColor_Request; // set default color for resource creation } | { oneofKind: undefined; }; @@ -873,9 +881,10 @@ export interface TxAPI_ServerMessage { } | { oneofKind: "cacheDeleteExpiredRecords"; /** - * @generated from protobuf field: MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Response cache_delete_expired_records = 301 + * @deprecated + * @generated from protobuf field: MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true] */ - cacheDeleteExpiredRecords: CacheAPI_DeleteExpiredRecords_Response; + cacheDeleteExpiredRecords: Util_Deprecated; } | { oneofKind: "controllerFeaturesSet"; /** @@ -888,6 +897,12 @@ export interface TxAPI_ServerMessage { * @generated from protobuf field: MiLaboratories.PL.API.ControllerAPI.ClearFeatures.Response controller_features_clear = 351 */ controllerFeaturesClear: ControllerAPI_ClearFeatures_Response; + } | { + oneofKind: "setDefaultColor"; + /** + * @generated from protobuf field: MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response set_default_color = 400 + */ + setDefaultColor: TxAPI_SetDefaultColor_Response; } | { oneofKind: undefined; }; @@ -1042,6 +1057,25 @@ export interface TxAPI_Sync_Request { */ export interface TxAPI_Sync_Response { } +/** + * @generated from protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor + */ +export interface TxAPI_SetDefaultColor { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request + */ +export interface TxAPI_SetDefaultColor_Request { + /** + * @generated from protobuf field: bytes color_proof = 1 + */ + colorProof: Uint8Array; +} +/** + * @generated from protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response + */ +export interface TxAPI_SetDefaultColor_Response { +} /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI */ @@ -1068,6 +1102,10 @@ export interface ResourceAPI_CreateStruct_Request { * @generated from protobuf field: optional bytes data = 4 */ data?: Uint8Array; + /** + * @generated from protobuf field: bytes color_proof = 5 + */ + colorProof: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateStruct.Response @@ -1077,6 +1115,10 @@ export interface ResourceAPI_CreateStruct_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateEphemeral @@ -1099,6 +1141,10 @@ export interface ResourceAPI_CreateEphemeral_Request { * @generated from protobuf field: optional bytes data = 4 */ data?: Uint8Array; + /** + * @generated from protobuf field: bytes color_proof = 5 + */ + colorProof: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateEphemeral.Response @@ -1108,6 +1154,10 @@ export interface ResourceAPI_CreateEphemeral_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateChild @@ -1165,6 +1215,10 @@ export interface ResourceAPI_CreateValue_Request { * @generated from protobuf field: bool error_if_exists = 7 */ errorIfExists: boolean; + /** + * @generated from protobuf field: bytes color_proof = 8 + */ + colorProof: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateValue.Response @@ -1174,6 +1228,10 @@ export interface ResourceAPI_CreateValue_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.GetValueID @@ -1201,6 +1259,10 @@ export interface ResourceAPI_GetValueID_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateSingleton @@ -1227,6 +1289,10 @@ export interface ResourceAPI_CreateSingleton_Request { * @generated from protobuf field: bool error_if_exists = 7 */ errorIfExists: boolean; + /** + * @generated from protobuf field: bytes color_proof = 8 + */ + colorProof: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateSingleton.Response @@ -1236,6 +1302,10 @@ export interface ResourceAPI_CreateSingleton_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.GetSingleton @@ -1281,6 +1351,10 @@ export interface ResourceAPI_LockInputs_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.LockInputs.Response @@ -1300,6 +1374,10 @@ export interface ResourceAPI_LockOutputs_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.LockOutputs.Response @@ -1319,6 +1397,10 @@ export interface ResourceAPI_Exists_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Exists.Response @@ -1342,10 +1424,18 @@ export interface ResourceAPI_SetError_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: uint64 error_resource_id = 2 */ errorResourceId: bigint; + /** + * @generated from protobuf field: bytes error_resource_signature = 4 + */ + errorResourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.SetError.Response @@ -1365,6 +1455,10 @@ export interface ResourceAPI_Get_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: bool load_fields = 2 */ @@ -1462,6 +1556,10 @@ export interface ResourceAPI_CreateRoot_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } // FIXME: add CreateResource method to API @@ -1477,9 +1575,13 @@ export interface ResourceAPI_Remove { */ export interface ResourceAPI_Remove_Request { /** - * @generated from protobuf field: uint64 id = 1 + * @generated from protobuf field: uint64 resource_id = 1 */ - id: bigint; + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Remove.Response @@ -1536,6 +1638,10 @@ export interface ResourceAPI_Name_Get_Response { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Name.Exists @@ -1592,6 +1698,10 @@ export interface ResourceAPI_Tree_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * Limit maximum depth the tree is traversed. * The resource is considered on depth = 0, the values of its fiels @@ -1628,6 +1738,10 @@ export interface ResourceAPI_TreeSize_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.TreeSize.Response @@ -1739,9 +1853,13 @@ export interface FieldAPI_SetError_Request { */ field?: FieldRef; /** - * @generated from protobuf field: uint64 err_resource_id = 2 + * @generated from protobuf field: uint64 error_resource_id = 2 + */ + errorResourceId: bigint; + /** + * @generated from protobuf field: bytes error_resource_signature = 3 */ - errResourceId: bigint; + errorResourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.FieldAPI.SetError.Response @@ -1822,6 +1940,10 @@ export interface FieldAPI_List_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; /** * Start the listing from given position, returning first field with * name >= start_from. @@ -2433,6 +2555,10 @@ export interface ResourceKVAPI_List_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; /** * Start the listing from given position, returning first item with * key >= start_from. @@ -2497,6 +2623,10 @@ export interface ResourceKVAPI_Set_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2524,6 +2654,10 @@ export interface ResourceKVAPI_Get_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2551,6 +2685,10 @@ export interface ResourceKVAPI_GetIfExists_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2582,6 +2720,10 @@ export interface ResourceKVAPI_Delete_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2605,6 +2747,10 @@ export interface ResourceKVAPI_SetFlag_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2632,6 +2778,10 @@ export interface ResourceKVAPI_GetFlag_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2850,24 +3000,85 @@ export interface CacheAPI_SetToField_Request { export interface CacheAPI_SetToField_Response { } /** - * @generated from protobuf message MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI */ -export interface CacheAPI_DeleteExpiredRecords { +export interface LocksAPI { } /** - * @generated from protobuf message MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Request + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues */ -export interface CacheAPI_DeleteExpiredRecords_Request { +export interface LocksAPI_LockFieldValues { } /** - * @generated from protobuf message MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Response + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create */ -export interface CacheAPI_DeleteExpiredRecords_Response { +export interface LocksAPI_LockFieldValues_Create { } /** - * @generated from protobuf message MiLaboratories.PL.API.LocksAPI + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Request */ -export interface LocksAPI { +export interface LocksAPI_LockFieldValues_Create_Request { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; // resource ID that will own the lock + /** + * @generated from protobuf field: repeated string lock_references_of = 2 + */ + lockReferencesOf: string[]; // list of fields, which values should be locked + /** + * @generated from protobuf field: string comment = 3 + */ + comment: string; // comment to show for other lockers on lock conflict. Truncated to 1024 bytes. +} +/** + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response + */ +export interface LocksAPI_LockFieldValues_Create_Response { + /** + * true when lock was acquired (new, or already owned by the owner) + * Client MUST pay attention to this flag, as it shows if lock was successfull. + * + * @generated from protobuf field: bool acquired = 1 + */ + acquired: boolean; + /** + * Info about why lock was not acquired. + * Limited number of conflicts is reported: i.e. if lock operation failed for 20 fields, only first 10 are listed here. + * The number '10' is not a fixed contract for external clients. It is just 'somehow truncated'. + * + * @generated from protobuf field: repeated MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo conflicting_locks = 2 + */ + conflictingLocks: LocksAPI_LockFieldValues_Create_Response_LockInfo[]; + /** + * @generated from protobuf field: bool conflicts_list_truncated = 3 + */ + conflictsListTruncated: boolean; // true if there were more conflicts than reported +} +/** + * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo + */ +export interface LocksAPI_LockFieldValues_Create_Response_LockInfo { + /** + * @generated from protobuf field: uint64 target_id = 1 + */ + targetId: bigint; // ID of resource that failed to be locked + /** + * @generated from protobuf field: string field_name = 2 + */ + fieldName: string; // what field was referencing this + /** + * @generated from protobuf field: uint64 locked_by = 3 + */ + lockedBy: bigint; // Who is owning the lock for + /** + * @generated from protobuf field: google.protobuf.Timestamp locked_at = 4 + */ + lockedAt?: Timestamp; // when the lock was obtained + /** + * @generated from protobuf field: string comment = 5 + */ + comment: string; // comment from the locker holding the lock } /** * @generated from protobuf message MiLaboratories.PL.API.LocksAPI.Lease @@ -2887,6 +3098,10 @@ export interface LocksAPI_Lease_Create_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 5 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: google.protobuf.Duration timeout = 3 */ @@ -2918,6 +3133,10 @@ export interface LocksAPI_Lease_Update_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 5 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: bytes lease_id = 2 */ @@ -2949,6 +3168,10 @@ export interface LocksAPI_Lease_Release_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: bytes lease_id = 2 */ @@ -3015,6 +3238,10 @@ export interface AuthAPI_GetJWTToken_Request { * @generated from protobuf field: google.protobuf.Duration expiration = 1 */ expiration?: Duration; + /** + * @generated from protobuf field: optional MiLaboratories.PL.API.AuthAPI.GetJWTToken.Role requested_role = 2 + */ + requestedRole?: AuthAPI_GetJWTToken_Role; } /** * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.GetJWTToken.Response @@ -3024,6 +3251,33 @@ export interface AuthAPI_GetJWTToken_Response { * @generated from protobuf field: string token = 1 */ token: string; + /** + * @generated from protobuf field: bytes session_id = 2 + */ + sessionId: Uint8Array; +} +/** + * @generated from protobuf enum MiLaboratories.PL.API.AuthAPI.GetJWTToken.Role + */ +export enum AuthAPI_GetJWTToken_Role { + /** + * issue JWT with caller's natural role + * + * @generated from protobuf enum value: ROLE_UNSPECIFIED = 0; + */ + ROLE_UNSPECIFIED = 0, + /** + * @generated from protobuf enum value: USER = 1; + */ + USER = 1, + /** + * @generated from protobuf enum value: CONTROLLER = 2; + */ + CONTROLLER = 2, + /** + * @generated from protobuf enum value: WORKFLOW = 3; + */ + WORKFLOW = 3 } /** * @generated from protobuf message MiLaboratories.PL.API.MiscAPI @@ -3150,6 +3404,16 @@ export interface MaintenanceAPI_License_Response { */ responseBody: Uint8Array; } +/** + * @generated from protobuf message MiLaboratories.PL.API.Util + */ +export interface Util { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.Util.Deprecated + */ +export interface Util_Deprecated { +} // @generated message type with reflection information, may provide speed optimized methods class TxAPI$Type extends MessageType { constructor() { @@ -3248,9 +3512,10 @@ class TxAPI_ClientMessage$Type extends MessageType { { no: 254, name: "controller_key_value_get_flag", kind: "message", oneof: "request", T: () => ControllerKVAPI_GetFlag_Request }, { no: 255, name: "controller_key_value_get_flag_if_exists", kind: "message", oneof: "request", T: () => ControllerKVAPI_GetFlagIfExists_Request }, { no: 300, name: "cache_set_to_field", kind: "message", oneof: "request", T: () => CacheAPI_SetToField_Request }, - { no: 301, name: "cache_delete_expired_records", kind: "message", oneof: "request", T: () => CacheAPI_DeleteExpiredRecords_Request }, + { no: 301, name: "cache_delete_expired_records", kind: "message", oneof: "request", T: () => Util_Deprecated }, { no: 350, name: "controller_features_set", kind: "message", oneof: "request", T: () => ControllerAPI_SetFeatures_Request }, - { no: 351, name: "controller_features_clear", kind: "message", oneof: "request", T: () => ControllerAPI_ClearFeatures_Request } + { no: 351, name: "controller_features_clear", kind: "message", oneof: "request", T: () => ControllerAPI_ClearFeatures_Request }, + { no: 400, name: "set_default_color", kind: "message", oneof: "request", T: () => TxAPI_SetDefaultColor_Request } ]); } create(value?: PartialMessage): TxAPI_ClientMessage { @@ -3599,10 +3864,10 @@ class TxAPI_ClientMessage$Type extends MessageType { cacheSetToField: CacheAPI_SetToField_Request.internalBinaryRead(reader, reader.uint32(), options, (message.request as any).cacheSetToField) }; break; - case /* MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Request cache_delete_expired_records */ 301: + case /* MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true] */ 301: message.request = { oneofKind: "cacheDeleteExpiredRecords", - cacheDeleteExpiredRecords: CacheAPI_DeleteExpiredRecords_Request.internalBinaryRead(reader, reader.uint32(), options, (message.request as any).cacheDeleteExpiredRecords) + cacheDeleteExpiredRecords: Util_Deprecated.internalBinaryRead(reader, reader.uint32(), options, (message.request as any).cacheDeleteExpiredRecords) }; break; case /* MiLaboratories.PL.API.ControllerAPI.SetFeatures.Request controller_features_set */ 350: @@ -3617,6 +3882,12 @@ class TxAPI_ClientMessage$Type extends MessageType { controllerFeaturesClear: ControllerAPI_ClearFeatures_Request.internalBinaryRead(reader, reader.uint32(), options, (message.request as any).controllerFeaturesClear) }; break; + case /* MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request set_default_color */ 400: + message.request = { + oneofKind: "setDefaultColor", + setDefaultColor: TxAPI_SetDefaultColor_Request.internalBinaryRead(reader, reader.uint32(), options, (message.request as any).setDefaultColor) + }; + break; default: let u = options.readUnknownField; if (u === "throw") @@ -3797,15 +4068,18 @@ class TxAPI_ClientMessage$Type extends MessageType { /* MiLaboratories.PL.API.CacheAPI.SetToField.Request cache_set_to_field = 300; */ if (message.request.oneofKind === "cacheSetToField") CacheAPI_SetToField_Request.internalBinaryWrite(message.request.cacheSetToField, writer.tag(300, WireType.LengthDelimited).fork(), options).join(); - /* MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Request cache_delete_expired_records = 301; */ + /* MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true]; */ if (message.request.oneofKind === "cacheDeleteExpiredRecords") - CacheAPI_DeleteExpiredRecords_Request.internalBinaryWrite(message.request.cacheDeleteExpiredRecords, writer.tag(301, WireType.LengthDelimited).fork(), options).join(); + Util_Deprecated.internalBinaryWrite(message.request.cacheDeleteExpiredRecords, writer.tag(301, WireType.LengthDelimited).fork(), options).join(); /* MiLaboratories.PL.API.ControllerAPI.SetFeatures.Request controller_features_set = 350; */ if (message.request.oneofKind === "controllerFeaturesSet") ControllerAPI_SetFeatures_Request.internalBinaryWrite(message.request.controllerFeaturesSet, writer.tag(350, WireType.LengthDelimited).fork(), options).join(); /* MiLaboratories.PL.API.ControllerAPI.ClearFeatures.Request controller_features_clear = 351; */ if (message.request.oneofKind === "controllerFeaturesClear") ControllerAPI_ClearFeatures_Request.internalBinaryWrite(message.request.controllerFeaturesClear, writer.tag(351, WireType.LengthDelimited).fork(), options).join(); + /* MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request set_default_color = 400; */ + if (message.request.oneofKind === "setDefaultColor") + TxAPI_SetDefaultColor_Request.internalBinaryWrite(message.request.setDefaultColor, writer.tag(400, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -3877,9 +4151,10 @@ class TxAPI_ServerMessage$Type extends MessageType { { no: 254, name: "controller_key_value_get_flag", kind: "message", oneof: "response", T: () => ControllerKVAPI_GetFlag_Response }, { no: 255, name: "controller_key_value_get_flag_if_exists", kind: "message", oneof: "response", T: () => ControllerKVAPI_GetFlagIfExists_Response }, { no: 300, name: "cache_set_to_field", kind: "message", oneof: "response", T: () => CacheAPI_SetToField_Response }, - { no: 301, name: "cache_delete_expired_records", kind: "message", oneof: "response", T: () => CacheAPI_DeleteExpiredRecords_Response }, + { no: 301, name: "cache_delete_expired_records", kind: "message", oneof: "response", T: () => Util_Deprecated }, { no: 350, name: "controller_features_set", kind: "message", oneof: "response", T: () => ControllerAPI_SetFeatures_Response }, { no: 351, name: "controller_features_clear", kind: "message", oneof: "response", T: () => ControllerAPI_ClearFeatures_Response }, + { no: 400, name: "set_default_color", kind: "message", oneof: "response", T: () => TxAPI_SetDefaultColor_Response }, { no: 3, name: "error", kind: "message", T: () => Status } ]); } @@ -4232,10 +4507,10 @@ class TxAPI_ServerMessage$Type extends MessageType { cacheSetToField: CacheAPI_SetToField_Response.internalBinaryRead(reader, reader.uint32(), options, (message.response as any).cacheSetToField) }; break; - case /* MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Response cache_delete_expired_records */ 301: + case /* MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true] */ 301: message.response = { oneofKind: "cacheDeleteExpiredRecords", - cacheDeleteExpiredRecords: CacheAPI_DeleteExpiredRecords_Response.internalBinaryRead(reader, reader.uint32(), options, (message.response as any).cacheDeleteExpiredRecords) + cacheDeleteExpiredRecords: Util_Deprecated.internalBinaryRead(reader, reader.uint32(), options, (message.response as any).cacheDeleteExpiredRecords) }; break; case /* MiLaboratories.PL.API.ControllerAPI.SetFeatures.Response controller_features_set */ 350: @@ -4250,6 +4525,12 @@ class TxAPI_ServerMessage$Type extends MessageType { controllerFeaturesClear: ControllerAPI_ClearFeatures_Response.internalBinaryRead(reader, reader.uint32(), options, (message.response as any).controllerFeaturesClear) }; break; + case /* MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response set_default_color */ 400: + message.response = { + oneofKind: "setDefaultColor", + setDefaultColor: TxAPI_SetDefaultColor_Response.internalBinaryRead(reader, reader.uint32(), options, (message.response as any).setDefaultColor) + }; + break; case /* google.rpc.Status error */ 3: message.error = Status.internalBinaryRead(reader, reader.uint32(), options, message.error); break; @@ -4439,15 +4720,18 @@ class TxAPI_ServerMessage$Type extends MessageType { /* MiLaboratories.PL.API.CacheAPI.SetToField.Response cache_set_to_field = 300; */ if (message.response.oneofKind === "cacheSetToField") CacheAPI_SetToField_Response.internalBinaryWrite(message.response.cacheSetToField, writer.tag(300, WireType.LengthDelimited).fork(), options).join(); - /* MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Response cache_delete_expired_records = 301; */ + /* MiLaboratories.PL.API.Util.Deprecated cache_delete_expired_records = 301 [deprecated = true]; */ if (message.response.oneofKind === "cacheDeleteExpiredRecords") - CacheAPI_DeleteExpiredRecords_Response.internalBinaryWrite(message.response.cacheDeleteExpiredRecords, writer.tag(301, WireType.LengthDelimited).fork(), options).join(); + Util_Deprecated.internalBinaryWrite(message.response.cacheDeleteExpiredRecords, writer.tag(301, WireType.LengthDelimited).fork(), options).join(); /* MiLaboratories.PL.API.ControllerAPI.SetFeatures.Response controller_features_set = 350; */ if (message.response.oneofKind === "controllerFeaturesSet") ControllerAPI_SetFeatures_Response.internalBinaryWrite(message.response.controllerFeaturesSet, writer.tag(350, WireType.LengthDelimited).fork(), options).join(); /* MiLaboratories.PL.API.ControllerAPI.ClearFeatures.Response controller_features_clear = 351; */ if (message.response.oneofKind === "controllerFeaturesClear") ControllerAPI_ClearFeatures_Response.internalBinaryWrite(message.response.controllerFeaturesClear, writer.tag(351, WireType.LengthDelimited).fork(), options).join(); + /* MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response set_default_color = 400; */ + if (message.response.oneofKind === "setDefaultColor") + TxAPI_SetDefaultColor_Response.internalBinaryWrite(message.response.setDefaultColor, writer.tag(400, WireType.LengthDelimited).fork(), options).join(); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -5143,6 +5427,129 @@ class TxAPI_Sync_Response$Type extends MessageType { */ export const TxAPI_Sync_Response = new TxAPI_Sync_Response$Type(); // @generated message type with reflection information, may provide speed optimized methods +class TxAPI_SetDefaultColor$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.TxAPI.SetDefaultColor", []); + } + create(value?: PartialMessage): TxAPI_SetDefaultColor { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: TxAPI_SetDefaultColor): TxAPI_SetDefaultColor { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: TxAPI_SetDefaultColor, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor + */ +export const TxAPI_SetDefaultColor = new TxAPI_SetDefaultColor$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class TxAPI_SetDefaultColor_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request", [ + { no: 1, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): TxAPI_SetDefaultColor_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + message.colorProof = new Uint8Array(0); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: TxAPI_SetDefaultColor_Request): TxAPI_SetDefaultColor_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bytes color_proof */ 1: + message.colorProof = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: TxAPI_SetDefaultColor_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bytes color_proof = 1; */ + if (message.colorProof.length) + writer.tag(1, WireType.LengthDelimited).bytes(message.colorProof); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request + */ +export const TxAPI_SetDefaultColor_Request = new TxAPI_SetDefaultColor_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class TxAPI_SetDefaultColor_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response", []); + } + create(value?: PartialMessage): TxAPI_SetDefaultColor_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: TxAPI_SetDefaultColor_Response): TxAPI_SetDefaultColor_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: TxAPI_SetDefaultColor_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response + */ +export const TxAPI_SetDefaultColor_Response = new TxAPI_SetDefaultColor_Response$Type(); +// @generated message type with reflection information, may provide speed optimized methods class ResourceAPI$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI", []); @@ -5224,12 +5631,14 @@ class ResourceAPI_CreateStruct_Request$Type extends MessageType ResourceType }, - { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } + { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, + { no: 5, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateStruct_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.id = 0n; + message.colorProof = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5248,6 +5657,9 @@ class ResourceAPI_CreateStruct_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.CreateStruct.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateStruct_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5301,6 +5718,9 @@ class ResourceAPI_CreateStruct_Response$Type extends MessageType ResourceType }, - { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } + { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, + { no: 5, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateEphemeral_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.id = 0n; + message.colorProof = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5394,6 +5819,9 @@ class ResourceAPI_CreateEphemeral_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.CreateEphemeral.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateEphemeral_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5447,6 +5880,9 @@ class ResourceAPI_CreateEphemeral_Response$Type extends MessageType ResourceType }, { no: 6, name: "data", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, - { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 8, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateValue_Request { @@ -5671,6 +6111,7 @@ class ResourceAPI_CreateValue_Request$Type extends MessageType(this, message, value); return message; @@ -5692,6 +6133,9 @@ class ResourceAPI_CreateValue_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.CreateValue.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateValue_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5748,6 +6197,9 @@ class ResourceAPI_CreateValue_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.GetValueID.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_GetValueID_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5887,6 +6344,9 @@ class ResourceAPI_GetValueID_Response$Type extends MessageType ResourceType }, { no: 6, name: "data", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, - { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 8, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateSingleton_Request { @@ -5965,6 +6429,7 @@ class ResourceAPI_CreateSingleton_Request$Type extends MessageType(this, message, value); return message; @@ -5986,6 +6451,9 @@ class ResourceAPI_CreateSingleton_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.CreateSingleton.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateSingleton_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6042,6 +6515,9 @@ class ResourceAPI_CreateSingleton_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.LockInputs.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_LockInputs_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6273,6 +6754,9 @@ class ResourceAPI_LockInputs_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.LockOutputs.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_LockOutputs_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6396,6 +6885,9 @@ class ResourceAPI_LockOutputs_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.Exists.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_Exists_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6519,6 +7016,9 @@ class ResourceAPI_Exists_Request$Type extends MessageType): ResourceAPI_SetError_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.errorResourceId = 0n; + message.errorResourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6653,9 +7160,15 @@ class ResourceAPI_SetError_Request$Type extends MessageType constructor() { super("MiLaboratories.PL.API.ResourceAPI.Get.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "load_fields", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): ResourceAPI_Get_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.loadFields = false; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -6784,6 +7305,9 @@ class ResourceAPI_Get_Request$Type extends MessageType case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 3: + message.resourceSignature = reader.bytes(); + break; case /* bool load_fields */ 2: message.loadFields = reader.bool(); break; @@ -6805,6 +7329,9 @@ class ResourceAPI_Get_Request$Type extends MessageType /* bool load_fields = 2; */ if (message.loadFields !== false) writer.tag(2, WireType.Varint).bool(message.loadFields); + /* bytes resource_signature = 3; */ + if (message.resourceSignature.length) + writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -7157,12 +7684,14 @@ export const ResourceAPI_CreateRoot_Request = new ResourceAPI_CreateRoot_Request class ResourceAPI_CreateRoot_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.CreateRoot.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateRoot_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7175,6 +7704,9 @@ class ResourceAPI_CreateRoot_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.Remove.Request", [ - { no: 1, name: "id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_Remove_Request { const message = globalThis.Object.create((this.messagePrototype!)); - message.id = 0n; + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7257,8 +7794,11 @@ class ResourceAPI_Remove_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.Name.Get.Response", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_Name_Get_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7599,6 +8144,9 @@ class ResourceAPI_Name_Get_Response$Type extends MessageType): ResourceAPI_Tree_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7940,6 +8493,9 @@ class ResourceAPI_Tree_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceAPI.TreeSize.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_TreeSize_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8077,6 +8638,9 @@ class ResourceAPI_TreeSize_Request$Type extends MessageType FieldRef }, - { no: 2, name: "err_resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 2, name: "error_resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "error_resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): FieldAPI_SetError_Request { const message = globalThis.Object.create((this.messagePrototype!)); - message.errResourceId = 0n; + message.errorResourceId = 0n; + message.errorResourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8654,8 +9223,11 @@ class FieldAPI_SetError_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.FieldAPI.List.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "start_from", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, { no: 3, name: "limit", kind: "scalar", T: 13 /*ScalarType.UINT32*/ } ]); @@ -9147,6 +9723,7 @@ class FieldAPI_List_Request$Type extends MessageType { create(value?: PartialMessage): FieldAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.startFrom = ""; message.limit = 0; if (value !== undefined) @@ -9161,6 +9738,9 @@ class FieldAPI_List_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 4: + message.resourceSignature = reader.bytes(); + break; case /* string start_from */ 2: message.startFrom = reader.string(); break; @@ -9188,6 +9768,9 @@ class FieldAPI_List_Request$Type extends MessageType { /* uint32 limit = 3; */ if (message.limit !== 0) writer.tag(3, WireType.Varint).uint32(message.limit); + /* bytes resource_signature = 4; */ + if (message.resourceSignature.length) + writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -12379,6 +12962,7 @@ class ResourceKVAPI_List_Request$Type extends MessageType): ResourceKVAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.startFrom = ""; message.limit = 0; if (value !== undefined) @@ -12400,6 +12985,9 @@ class ResourceKVAPI_List_Request$Type extends MessageType): ResourceKVAPI_Set_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; message.value = new Uint8Array(0); if (value !== undefined) @@ -12610,6 +13203,9 @@ class ResourceKVAPI_Set_Request$Type extends MessageType): ResourceKVAPI_Get_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -12747,6 +13348,9 @@ class ResourceKVAPI_Get_Request$Type extends MessageType): ResourceKVAPI_GetIfExists_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -12887,6 +13496,9 @@ class ResourceKVAPI_GetIfExists_Request$Type extends MessageType): ResourceKVAPI_Delete_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -13035,6 +13652,9 @@ class ResourceKVAPI_Delete_Request$Type extends MessageType): ResourceKVAPI_SetFlag_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; message.value = false; if (value !== undefined) @@ -13168,6 +13793,9 @@ class ResourceKVAPI_SetFlag_Request$Type extends MessageType): ResourceKVAPI_GetFlag_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -13305,6 +13938,9 @@ class ResourceKVAPI_GetFlag_Request$Type extends MessageType { +class LocksAPI$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords", []); + super("MiLaboratories.PL.API.LocksAPI", []); } - create(value?: PartialMessage): CacheAPI_DeleteExpiredRecords { + create(value?: PartialMessage): LocksAPI { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CacheAPI_DeleteExpiredRecords): CacheAPI_DeleteExpiredRecords { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI): LocksAPI { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -14469,7 +15108,7 @@ class CacheAPI_DeleteExpiredRecords$Type extends MessageType { +class LocksAPI_LockFieldValues$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Request", []); + super("MiLaboratories.PL.API.LocksAPI.LockFieldValues", []); } - create(value?: PartialMessage): CacheAPI_DeleteExpiredRecords_Request { + create(value?: PartialMessage): LocksAPI_LockFieldValues { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CacheAPI_DeleteExpiredRecords_Request): CacheAPI_DeleteExpiredRecords_Request { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI_LockFieldValues): LocksAPI_LockFieldValues { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -14507,7 +15146,7 @@ class CacheAPI_DeleteExpiredRecords_Request$Type extends MessageType { +class LocksAPI_LockFieldValues_Create$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.CacheAPI.DeleteExpiredRecords.Response", []); + super("MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create", []); } - create(value?: PartialMessage): CacheAPI_DeleteExpiredRecords_Response { + create(value?: PartialMessage): LocksAPI_LockFieldValues_Create { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: CacheAPI_DeleteExpiredRecords_Response): CacheAPI_DeleteExpiredRecords_Response { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI_LockFieldValues_Create): LocksAPI_LockFieldValues_Create { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -14545,7 +15184,7 @@ class CacheAPI_DeleteExpiredRecords_Response$Type extends MessageType { +class LocksAPI_LockFieldValues_Create_Request$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.LocksAPI", []); + super("MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Request", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "lock_references_of", kind: "scalar", repeat: 2 /*RepeatType.UNPACKED*/, T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "comment", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); } - create(value?: PartialMessage): LocksAPI { + create(value?: PartialMessage): LocksAPI_LockFieldValues_Create_Request { const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.lockReferencesOf = []; + message.comment = ""; if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI): LocksAPI { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI_LockFieldValues_Create_Request): LocksAPI_LockFieldValues_Create_Request { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* repeated string lock_references_of */ 2: + message.lockReferencesOf.push(reader.string()); + break; + case /* string comment */ 3: + message.comment = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -14583,7 +15238,16 @@ class LocksAPI$Type extends MessageType { } return message; } - internalBinaryWrite(message: LocksAPI, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + internalBinaryWrite(message: LocksAPI_LockFieldValues_Create_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* repeated string lock_references_of = 2; */ + for (let i = 0; i < message.lockReferencesOf.length; i++) + writer.tag(2, WireType.LengthDelimited).string(message.lockReferencesOf[i]); + /* string comment = 3; */ + if (message.comment !== "") + writer.tag(3, WireType.LengthDelimited).string(message.comment); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -14591,9 +15255,150 @@ class LocksAPI$Type extends MessageType { } } /** - * @generated MessageType for protobuf message MiLaboratories.PL.API.LocksAPI + * @generated MessageType for protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Request */ -export const LocksAPI = new LocksAPI$Type(); +export const LocksAPI_LockFieldValues_Create_Request = new LocksAPI_LockFieldValues_Create_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class LocksAPI_LockFieldValues_Create_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response", [ + { no: 1, name: "acquired", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 2, name: "conflicting_locks", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => LocksAPI_LockFieldValues_Create_Response_LockInfo }, + { no: 3, name: "conflicts_list_truncated", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): LocksAPI_LockFieldValues_Create_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + message.acquired = false; + message.conflictingLocks = []; + message.conflictsListTruncated = false; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI_LockFieldValues_Create_Response): LocksAPI_LockFieldValues_Create_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bool acquired */ 1: + message.acquired = reader.bool(); + break; + case /* repeated MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo conflicting_locks */ 2: + message.conflictingLocks.push(LocksAPI_LockFieldValues_Create_Response_LockInfo.internalBinaryRead(reader, reader.uint32(), options)); + break; + case /* bool conflicts_list_truncated */ 3: + message.conflictsListTruncated = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: LocksAPI_LockFieldValues_Create_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bool acquired = 1; */ + if (message.acquired !== false) + writer.tag(1, WireType.Varint).bool(message.acquired); + /* repeated MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo conflicting_locks = 2; */ + for (let i = 0; i < message.conflictingLocks.length; i++) + LocksAPI_LockFieldValues_Create_Response_LockInfo.internalBinaryWrite(message.conflictingLocks[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* bool conflicts_list_truncated = 3; */ + if (message.conflictsListTruncated !== false) + writer.tag(3, WireType.Varint).bool(message.conflictsListTruncated); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response + */ +export const LocksAPI_LockFieldValues_Create_Response = new LocksAPI_LockFieldValues_Create_Response$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class LocksAPI_LockFieldValues_Create_Response_LockInfo$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo", [ + { no: 1, name: "target_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "field_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 3, name: "locked_by", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "locked_at", kind: "message", T: () => Timestamp }, + { no: 5, name: "comment", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); + } + create(value?: PartialMessage): LocksAPI_LockFieldValues_Create_Response_LockInfo { + const message = globalThis.Object.create((this.messagePrototype!)); + message.targetId = 0n; + message.fieldName = ""; + message.lockedBy = 0n; + message.comment = ""; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: LocksAPI_LockFieldValues_Create_Response_LockInfo): LocksAPI_LockFieldValues_Create_Response_LockInfo { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 target_id */ 1: + message.targetId = reader.uint64().toBigInt(); + break; + case /* string field_name */ 2: + message.fieldName = reader.string(); + break; + case /* uint64 locked_by */ 3: + message.lockedBy = reader.uint64().toBigInt(); + break; + case /* google.protobuf.Timestamp locked_at */ 4: + message.lockedAt = Timestamp.internalBinaryRead(reader, reader.uint32(), options, message.lockedAt); + break; + case /* string comment */ 5: + message.comment = reader.string(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: LocksAPI_LockFieldValues_Create_Response_LockInfo, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 target_id = 1; */ + if (message.targetId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.targetId); + /* string field_name = 2; */ + if (message.fieldName !== "") + writer.tag(2, WireType.LengthDelimited).string(message.fieldName); + /* uint64 locked_by = 3; */ + if (message.lockedBy !== 0n) + writer.tag(3, WireType.Varint).uint64(message.lockedBy); + /* google.protobuf.Timestamp locked_at = 4; */ + if (message.lockedAt) + Timestamp.internalBinaryWrite(message.lockedAt, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + /* string comment = 5; */ + if (message.comment !== "") + writer.tag(5, WireType.LengthDelimited).string(message.comment); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.LocksAPI.LockFieldValues.Create.Response.LockInfo + */ +export const LocksAPI_LockFieldValues_Create_Response_LockInfo = new LocksAPI_LockFieldValues_Create_Response_LockInfo$Type(); // @generated message type with reflection information, may provide speed optimized methods class LocksAPI_Lease$Type extends MessageType { constructor() { @@ -14675,6 +15480,7 @@ class LocksAPI_Lease_Create_Request$Type extends MessageType Duration }, { no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); @@ -14682,6 +15488,7 @@ class LocksAPI_Lease_Create_Request$Type extends MessageType): LocksAPI_Lease_Create_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.name = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -14695,6 +15502,9 @@ class LocksAPI_Lease_Create_Request$Type extends MessageType Duration }, { no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } @@ -14830,6 +15644,7 @@ class LocksAPI_Lease_Update_Request$Type extends MessageType): LocksAPI_Lease_Update_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.leaseId = new Uint8Array(0); message.name = ""; if (value !== undefined) @@ -14844,6 +15659,9 @@ class LocksAPI_Lease_Update_Request$Type extends MessageType): LocksAPI_Lease_Release_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.leaseId = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); @@ -14987,6 +15810,9 @@ class LocksAPI_Lease_Release_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.AuthAPI.GetJWTToken.Request", [ - { no: 1, name: "expiration", kind: "message", T: () => Duration } + { no: 1, name: "expiration", kind: "message", T: () => Duration }, + { no: 2, name: "requested_role", kind: "enum", opt: true, T: () => ["MiLaboratories.PL.API.AuthAPI.GetJWTToken.Role", AuthAPI_GetJWTToken_Role] } ]); } create(value?: PartialMessage): AuthAPI_GetJWTToken_Request { @@ -15355,6 +16185,9 @@ class AuthAPI_GetJWTToken_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.AuthAPI.GetJWTToken.Response", [ - { no: 1, name: "token", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + { no: 1, name: "token", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "session_id", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): AuthAPI_GetJWTToken_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.token = ""; + message.sessionId = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -15402,6 +16240,9 @@ class AuthAPI_GetJWTToken_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.Util", []); + } + create(value?: PartialMessage): Util { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Util): Util { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Util, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.Util + */ +export const Util = new Util$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class Util_Deprecated$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.Util.Deprecated", []); + } + create(value?: PartialMessage): Util_Deprecated { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Util_Deprecated): Util_Deprecated { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: Util_Deprecated, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.Util.Deprecated + */ +export const Util_Deprecated = new Util_Deprecated$Type(); /** * @generated ServiceType for protobuf service MiLaboratories.PL.API.Platform */ @@ -15966,9 +16886,10 @@ export const Platform = new ServiceType("MiLaboratories.PL.API.Platform", [ { name: "GetControllerUrl", options: { "google.api.http": { post: "/v1/controller/url", body: "*" } }, I: ControllerAPI_GetUrl_Request, O: ControllerAPI_GetUrl_Response }, { name: "ControllerSetFeatures", options: { "google.api.http": { post: "/v1/controller/features", body: "*" } }, I: ControllerAPI_SetFeatures_Request, O: ControllerAPI_SetFeatures_Response }, { name: "ControllerClearFeatures", options: { "google.api.http": { delete: "/v1/controller/features", body: "*" } }, I: ControllerAPI_ClearFeatures_Request, O: ControllerAPI_ClearFeatures_Response }, - { name: "LeaseResource", options: { "google.api.http": { post: "/v1/locks/lease", body: "*" } }, I: LocksAPI_Lease_Create_Request, O: LocksAPI_Lease_Create_Response }, - { name: "UpdateLease", options: { "google.api.http": { put: "/v1/locks/lease", body: "*" } }, I: LocksAPI_Lease_Update_Request, O: LocksAPI_Lease_Update_Response }, - { name: "ReleaseLease", options: { "google.api.http": { delete: "/v1/locks/lease", body: "*" } }, I: LocksAPI_Lease_Release_Request, O: LocksAPI_Lease_Release_Response }, + { name: "LockFieldValues", options: { "google.api.http": { post: "/v1/locks/lock/create", body: "*" } }, I: LocksAPI_LockFieldValues_Create_Request, O: LocksAPI_LockFieldValues_Create_Response }, + { name: "LeaseResource", options: { "google.api.http": { post: "/v1/locks/lease/create", body: "*" } }, I: LocksAPI_Lease_Create_Request, O: LocksAPI_Lease_Create_Response }, + { name: "UpdateLease", options: { "google.api.http": { post: "/v1/locks/lease/update", body: "*" } }, I: LocksAPI_Lease_Update_Request, O: LocksAPI_Lease_Update_Response }, + { name: "ReleaseLease", options: { "google.api.http": { post: "/v1/locks/lease/release", body: "*" } }, I: LocksAPI_Lease_Release_Request, O: LocksAPI_Lease_Release_Response }, { name: "AuthMethods", options: { "google.api.http": { get: "/v1/auth/methods" } }, I: AuthAPI_ListMethods_Request, O: AuthAPI_ListMethods_Response }, { name: "GetJWTToken", options: { "google.api.http": { post: "/v1/auth/jwt-token", body: "*" } }, I: AuthAPI_GetJWTToken_Request, O: AuthAPI_GetJWTToken_Response }, { name: "ListResourceTypes", options: { "google.api.http": { get: "/v1/resource-types" } }, I: MiscAPI_ListResourceTypes_Request, O: MiscAPI_ListResourceTypes_Response }, diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts index 8d1007d191..fe6e013161 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api_types.ts @@ -33,9 +33,13 @@ export interface Tx { */ export interface Resource { /** - * @generated from protobuf field: uint64 id = 2 + * @generated from protobuf field: uint64 resource_id = 2 */ - id: bigint; + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 18 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: bytes canonical_id = 17 */ @@ -153,6 +157,13 @@ export interface Field { * @generated from protobuf field: uint64 value = 5 */ value: bigint; + /** + * Signature for value resource ID, inheriting the parent resource's color. + * Populated server-side when the parent resource has a known color in the current TX. + * + * @generated from protobuf field: bytes value_signature = 10 + */ + valueSignature: Uint8Array; /** * If the value was empty, assigned or finally resolved. * @@ -172,6 +183,12 @@ export interface Field { * @generated from protobuf field: uint64 error = 6 */ error: bigint; + /** + * Signature for error resource ID, inheriting the parent resource's color. + * + * @generated from protobuf field: bytes error_signature = 11 + */ + errorSignature: Uint8Array; } /** * @generated from protobuf enum MiLaboratories.PL.API.Field.ValueStatus @@ -301,6 +318,10 @@ export interface Notification_Events { * @generated from protobuf field: bool dynamic_changed = 10 */ dynamicChanged: boolean; + /** + * @generated from protobuf field: bool resource_recovered = 17 + */ + resourceRecovered: boolean; } /** * @generated from protobuf message MiLaboratories.PL.API.Notification.FieldChange @@ -372,6 +393,67 @@ export interface ResourceSchema { * @generated from protobuf field: repeated MiLaboratories.PL.API.FieldSchema fields = 2 */ fields: FieldSchema[]; + /** + * Access restriction flags for non-controller roles + * + * @generated from protobuf field: optional MiLaboratories.PL.API.ResourceSchema.AccessFlags access_flags = 3 + */ + accessFlags?: ResourceSchema_AccessFlags; + /** + * @generated from protobuf field: bool free_inputs = 4 + */ + freeInputs: boolean; // if true, skip automatic input locking on creation + /** + * @generated from protobuf field: bool free_outputs = 5 + */ + freeOutputs: boolean; // if true, skip automatic output locking on creation +} +/** + * @generated from protobuf message MiLaboratories.PL.API.ResourceSchema.AccessFlags + */ +export interface ResourceSchema_AccessFlags { + /** + * Deny-list approach: default = allowed (true) + * Controllers set these to false to restrict non-controller roles (role='u', role='w') + * + * @generated from protobuf field: optional bool create_resource = 1 + */ + createResource?: boolean; // default: true (creation allowed) + /** + * IMPORTANT: read_fields=false with write_fields=true is a forbidden combination + * + * @generated from protobuf field: optional bool read_fields = 2 + */ + readFields?: boolean; // default: true (reads allowed) + /** + * @generated from protobuf field: optional bool write_fields = 3 + */ + writeFields?: boolean; // default: true (writes allowed) + /** + * IMPORTANT: read_kv=false with write_kv=true is a forbidden combination + * + * @generated from protobuf field: optional bool read_kv = 4 + */ + readKv?: boolean; // if unset, falls back to read_fields + /** + * @generated from protobuf field: optional bool write_kv = 5 + */ + writeKv?: boolean; // if unset, falls back to write_fields + /** + * Per-field-type overrides (map: field_type → bool) + * When defined for a field type, overrides resource-level flags + * + * @generated from protobuf field: map read_by_field_type = 6 + */ + readByFieldType: { + [key: number]: boolean; + }; + /** + * @generated from protobuf field: map write_by_field_type = 7 + */ + writeByFieldType: { + [key: number]: boolean; + }; } /** * @generated from protobuf message MiLaboratories.PL.API.FieldSchema @@ -466,7 +548,8 @@ export const Tx = new Tx$Type(); class Resource$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.Resource", [ - { no: 2, name: "id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 18, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 17, name: "canonical_id", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 3, name: "kind", kind: "enum", T: () => ["MiLaboratories.PL.API.Resource.Kind", Resource_Kind, "KIND_"] }, { no: 4, name: "type", kind: "message", T: () => ResourceType }, @@ -486,7 +569,8 @@ class Resource$Type extends MessageType { } create(value?: PartialMessage): Resource { const message = globalThis.Object.create((this.messagePrototype!)); - message.id = 0n; + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.canonicalId = new Uint8Array(0); message.kind = 0; message.data = new Uint8Array(0); @@ -507,8 +591,11 @@ class Resource$Type extends MessageType { while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { - case /* uint64 id */ 2: - message.id = reader.uint64().toBigInt(); + case /* uint64 resource_id */ 2: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 18: + message.resourceSignature = reader.bytes(); break; case /* bytes canonical_id */ 17: message.canonicalId = reader.bytes(); @@ -567,9 +654,9 @@ class Resource$Type extends MessageType { return message; } internalBinaryWrite(message: Resource, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* uint64 id = 2; */ - if (message.id !== 0n) - writer.tag(2, WireType.Varint).uint64(message.id); + /* uint64 resource_id = 2; */ + if (message.resourceId !== 0n) + writer.tag(2, WireType.Varint).uint64(message.resourceId); /* MiLaboratories.PL.API.Resource.Kind kind = 3; */ if (message.kind !== 0) writer.tag(3, WireType.Varint).int32(message.kind); @@ -615,6 +702,9 @@ class Resource$Type extends MessageType { /* bytes canonical_id = 17; */ if (message.canonicalId.length) writer.tag(17, WireType.LengthDelimited).bytes(message.canonicalId); + /* bytes resource_signature = 18; */ + if (message.resourceSignature.length) + writer.tag(18, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -680,18 +770,22 @@ class Field$Type extends MessageType { { no: 2, name: "type", kind: "enum", T: () => ["MiLaboratories.PL.Base.FieldType", FieldType] }, { no: 3, name: "features", kind: "message", T: () => Resource_Features }, { no: 5, name: "value", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 10, name: "value_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 7, name: "value_status", kind: "enum", T: () => ["MiLaboratories.PL.API.Field.ValueStatus", Field_ValueStatus] }, { no: 8, name: "value_is_final", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, - { no: 6, name: "error", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 6, name: "error", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 11, name: "error_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): Field { const message = globalThis.Object.create((this.messagePrototype!)); message.type = 0; message.value = 0n; + message.valueSignature = new Uint8Array(0); message.valueStatus = 0; message.valueIsFinal = false; message.error = 0n; + message.errorSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -713,6 +807,9 @@ class Field$Type extends MessageType { case /* uint64 value */ 5: message.value = reader.uint64().toBigInt(); break; + case /* bytes value_signature */ 10: + message.valueSignature = reader.bytes(); + break; case /* MiLaboratories.PL.API.Field.ValueStatus value_status */ 7: message.valueStatus = reader.int32(); break; @@ -722,6 +819,9 @@ class Field$Type extends MessageType { case /* uint64 error */ 6: message.error = reader.uint64().toBigInt(); break; + case /* bytes error_signature */ 11: + message.errorSignature = reader.bytes(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -755,6 +855,12 @@ class Field$Type extends MessageType { /* bool value_is_final = 8; */ if (message.valueIsFinal !== false) writer.tag(8, WireType.Varint).bool(message.valueIsFinal); + /* bytes value_signature = 10; */ + if (message.valueSignature.length) + writer.tag(10, WireType.LengthDelimited).bytes(message.valueSignature); + /* bytes error_signature = 11; */ + if (message.errorSignature.length) + writer.tag(11, WireType.LengthDelimited).bytes(message.errorSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -910,7 +1016,8 @@ class Notification_Events$Type extends MessageType { { no: 8, name: "output_set", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, { no: 9, name: "all_outputs_set", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, { no: 14, name: "generic_otw_set", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, - { no: 10, name: "dynamic_changed", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + { no: 10, name: "dynamic_changed", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 17, name: "resource_recovered", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): Notification_Events { @@ -930,6 +1037,7 @@ class Notification_Events$Type extends MessageType { message.allOutputsSet = false; message.genericOtwSet = false; message.dynamicChanged = false; + message.resourceRecovered = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -984,6 +1092,9 @@ class Notification_Events$Type extends MessageType { case /* bool dynamic_changed */ 10: message.dynamicChanged = reader.bool(); break; + case /* bool resource_recovered */ 17: + message.resourceRecovered = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -1041,6 +1152,9 @@ class Notification_Events$Type extends MessageType { /* bool field_got_error = 16; */ if (message.fieldGotError !== false) writer.tag(16, WireType.Varint).bool(message.fieldGotError); + /* bool resource_recovered = 17; */ + if (message.resourceRecovered !== false) + writer.tag(17, WireType.Varint).bool(message.resourceRecovered); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -1298,12 +1412,17 @@ class ResourceSchema$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.ResourceSchema", [ { no: 1, name: "type", kind: "message", T: () => ResourceType }, - { no: 2, name: "fields", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => FieldSchema } + { no: 2, name: "fields", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => FieldSchema }, + { no: 3, name: "access_flags", kind: "message", T: () => ResourceSchema_AccessFlags }, + { no: 4, name: "free_inputs", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, + { no: 5, name: "free_outputs", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): ResourceSchema { const message = globalThis.Object.create((this.messagePrototype!)); message.fields = []; + message.freeInputs = false; + message.freeOutputs = false; if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1319,6 +1438,15 @@ class ResourceSchema$Type extends MessageType { case /* repeated MiLaboratories.PL.API.FieldSchema fields */ 2: message.fields.push(FieldSchema.internalBinaryRead(reader, reader.uint32(), options)); break; + case /* optional MiLaboratories.PL.API.ResourceSchema.AccessFlags access_flags */ 3: + message.accessFlags = ResourceSchema_AccessFlags.internalBinaryRead(reader, reader.uint32(), options, message.accessFlags); + break; + case /* bool free_inputs */ 4: + message.freeInputs = reader.bool(); + break; + case /* bool free_outputs */ 5: + message.freeOutputs = reader.bool(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -1337,6 +1465,15 @@ class ResourceSchema$Type extends MessageType { /* repeated MiLaboratories.PL.API.FieldSchema fields = 2; */ for (let i = 0; i < message.fields.length; i++) FieldSchema.internalBinaryWrite(message.fields[i], writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* optional MiLaboratories.PL.API.ResourceSchema.AccessFlags access_flags = 3; */ + if (message.accessFlags) + ResourceSchema_AccessFlags.internalBinaryWrite(message.accessFlags, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* bool free_inputs = 4; */ + if (message.freeInputs !== false) + writer.tag(4, WireType.Varint).bool(message.freeInputs); + /* bool free_outputs = 5; */ + if (message.freeOutputs !== false) + writer.tag(5, WireType.Varint).bool(message.freeOutputs); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -1348,6 +1485,128 @@ class ResourceSchema$Type extends MessageType { */ export const ResourceSchema = new ResourceSchema$Type(); // @generated message type with reflection information, may provide speed optimized methods +class ResourceSchema_AccessFlags$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.ResourceSchema.AccessFlags", [ + { no: 1, name: "create_resource", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 2, name: "read_fields", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 3, name: "write_fields", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 4, name: "read_kv", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 5, name: "write_kv", kind: "scalar", opt: true, T: 8 /*ScalarType.BOOL*/ }, + { no: 6, name: "read_by_field_type", kind: "map", K: 13 /*ScalarType.UINT32*/, V: { kind: "scalar", T: 8 /*ScalarType.BOOL*/ } }, + { no: 7, name: "write_by_field_type", kind: "map", K: 13 /*ScalarType.UINT32*/, V: { kind: "scalar", T: 8 /*ScalarType.BOOL*/ } } + ]); + } + create(value?: PartialMessage): ResourceSchema_AccessFlags { + const message = globalThis.Object.create((this.messagePrototype!)); + message.readByFieldType = {}; + message.writeByFieldType = {}; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ResourceSchema_AccessFlags): ResourceSchema_AccessFlags { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* optional bool create_resource */ 1: + message.createResource = reader.bool(); + break; + case /* optional bool read_fields */ 2: + message.readFields = reader.bool(); + break; + case /* optional bool write_fields */ 3: + message.writeFields = reader.bool(); + break; + case /* optional bool read_kv */ 4: + message.readKv = reader.bool(); + break; + case /* optional bool write_kv */ 5: + message.writeKv = reader.bool(); + break; + case /* map read_by_field_type */ 6: + this.binaryReadMap6(message.readByFieldType, reader, options); + break; + case /* map write_by_field_type */ 7: + this.binaryReadMap7(message.writeByFieldType, reader, options); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + private binaryReadMap6(map: ResourceSchema_AccessFlags["readByFieldType"], reader: IBinaryReader, options: BinaryReadOptions): void { + let len = reader.uint32(), end = reader.pos + len, key: keyof ResourceSchema_AccessFlags["readByFieldType"] | undefined, val: ResourceSchema_AccessFlags["readByFieldType"][any] | undefined; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case 1: + key = reader.uint32(); + break; + case 2: + val = reader.bool(); + break; + default: throw new globalThis.Error("unknown map entry field for MiLaboratories.PL.API.ResourceSchema.AccessFlags.read_by_field_type"); + } + } + map[key ?? 0] = val ?? false; + } + private binaryReadMap7(map: ResourceSchema_AccessFlags["writeByFieldType"], reader: IBinaryReader, options: BinaryReadOptions): void { + let len = reader.uint32(), end = reader.pos + len, key: keyof ResourceSchema_AccessFlags["writeByFieldType"] | undefined, val: ResourceSchema_AccessFlags["writeByFieldType"][any] | undefined; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case 1: + key = reader.uint32(); + break; + case 2: + val = reader.bool(); + break; + default: throw new globalThis.Error("unknown map entry field for MiLaboratories.PL.API.ResourceSchema.AccessFlags.write_by_field_type"); + } + } + map[key ?? 0] = val ?? false; + } + internalBinaryWrite(message: ResourceSchema_AccessFlags, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* optional bool create_resource = 1; */ + if (message.createResource !== undefined) + writer.tag(1, WireType.Varint).bool(message.createResource); + /* optional bool read_fields = 2; */ + if (message.readFields !== undefined) + writer.tag(2, WireType.Varint).bool(message.readFields); + /* optional bool write_fields = 3; */ + if (message.writeFields !== undefined) + writer.tag(3, WireType.Varint).bool(message.writeFields); + /* optional bool read_kv = 4; */ + if (message.readKv !== undefined) + writer.tag(4, WireType.Varint).bool(message.readKv); + /* optional bool write_kv = 5; */ + if (message.writeKv !== undefined) + writer.tag(5, WireType.Varint).bool(message.writeKv); + /* map read_by_field_type = 6; */ + for (let k of globalThis.Object.keys(message.readByFieldType)) + writer.tag(6, WireType.LengthDelimited).fork().tag(1, WireType.Varint).uint32(parseInt(k)).tag(2, WireType.Varint).bool(message.readByFieldType[k as any]).join(); + /* map write_by_field_type = 7; */ + for (let k of globalThis.Object.keys(message.writeByFieldType)) + writer.tag(7, WireType.LengthDelimited).fork().tag(1, WireType.Varint).uint32(parseInt(k)).tag(2, WireType.Varint).bool(message.writeByFieldType[k as any]).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.ResourceSchema.AccessFlags + */ +export const ResourceSchema_AccessFlags = new ResourceSchema_AccessFlags$Type(); +// @generated message type with reflection information, may provide speed optimized methods class FieldSchema$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.FieldSchema", [ diff --git a/lib/node/pl-client/src/proto-grpc/google/protobuf/descriptor.ts b/lib/node/pl-client/src/proto-grpc/google/protobuf/descriptor.ts index 7425d2d606..b738fff32d 100644 --- a/lib/node/pl-client/src/proto-grpc/google/protobuf/descriptor.ts +++ b/lib/node/pl-client/src/proto-grpc/google/protobuf/descriptor.ts @@ -1260,8 +1260,6 @@ export enum FieldOptions_JSType { } /** * If set to RETENTION_SOURCE, the option will be omitted from the binary. - * Note: as of January 2023, support for this is in progress and does not yet - * have an effect (b/264593489). * * @generated from protobuf enum google.protobuf.FieldOptions.OptionRetention */ @@ -1282,8 +1280,7 @@ export enum FieldOptions_OptionRetention { /** * This indicates the types of entities that the field may apply to when used * as an option. If it is unset, then the field may be freely used as an - * option on any kind of entity. Note: as of January 2023, support for this is - * in progress and does not yet have an effect (b/264593489). + * option on any kind of entity. * * @generated from protobuf enum google.protobuf.FieldOptions.OptionTargetType */ @@ -2070,7 +2067,7 @@ export enum Edition { EDITION_2024 = 1001, /** * Placeholder editions for testing feature resolution. These should not be - * used or relyed on outside of tests. + * used or relied on outside of tests. * * @generated from protobuf enum value: EDITION_1_TEST_ONLY = 1; */ diff --git a/lib/node/pl-client/src/proto-grpc/google/protobuf/struct.ts b/lib/node/pl-client/src/proto-grpc/google/protobuf/struct.ts index 10568466a5..9dd3e4733b 100644 --- a/lib/node/pl-client/src/proto-grpc/google/protobuf/struct.ts +++ b/lib/node/pl-client/src/proto-grpc/google/protobuf/struct.ts @@ -178,7 +178,7 @@ class Struct$Type extends MessageType { /** * Encode `Struct` to JSON object. */ - internalJsonWrite(message: Struct, _options: JsonWriteOptions): JsonValue { + internalJsonWrite(message: Struct, options: JsonWriteOptions): JsonValue { let json: JsonObject = {}; for (let [k, v] of Object.entries(message.fields)) { json[k] = Value.toJson(v); From 2cd0a79e83b0bbaae5df4741035383d1d344cc56 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 13:41:15 +0100 Subject: [PATCH 02/27] MILAB-5815: autgen: changeset --- .changeset/cuddly-dancers-greet.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .changeset/cuddly-dancers-greet.md diff --git a/.changeset/cuddly-dancers-greet.md b/.changeset/cuddly-dancers-greet.md new file mode 100644 index 0000000000..0b0df67130 --- /dev/null +++ b/.changeset/cuddly-dancers-greet.md @@ -0,0 +1,26 @@ +--- +"@milaboratories/pl-client": major +--- + +Sync pl proto API: resource signing, locks, access control, field renames. + +**Breaking changes (proto field renames):** +- `Resource.id` → `Resource.resource_id` +- `ResourceAPI.Remove.Request.id` → `ResourceAPI.Remove.Request.resource_id` +- `FieldAPI.SetError.Request.err_resource_id` → `FieldAPI.SetError.Request.error_resource_id` +- `CacheAPI.DeleteExpiredRecords` deprecated (replaced with `Util.Deprecated`) + +**New proto fields — resource signing (color proof):** +- `Resource.resource_signature` — signature for resource ID +- `Field.value_signature`, `Field.error_signature` — signatures for field references +- `resource_signature` added to most request/response messages (Remove, Get, LockInputs, LockOutputs, Exists, SetError, Tree, TreeSize, FieldList, KV operations, Lease) +- `color_proof` added to resource creation requests (CreateStruct, CreateEphemeral, CreateValue, CreateSingleton, CreateRoot) +- `TxAPI.SetDefaultColor` — new TX operation to set default color for resource creation + +**New API features:** +- `LocksAPI.LockFieldValues` — optimistic locking for resolved field values +- `ResourceSchema.AccessFlags` — per-resource-type access restrictions for non-controller roles (create, read/write fields, read/write KV, per-field-type overrides) +- `ResourceSchema.free_inputs` / `free_outputs` — skip automatic locking on creation +- `Notification.Events.resource_recovered` — new notification event +- `AuthAPI.GetJWTToken.Role` — request JWT with specific role +- `AuthAPI.GetJWTToken.Response.session_id` — session ID in JWT response From 110a697909b9430fd7fdc9838db062de913a8e4d Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 13:57:31 +0100 Subject: [PATCH 03/27] MILAB-5815: fix: fields required by updated protobuf types --- lib/node/pl-client/src/core/transaction.ts | 26 +++++++++++++--------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index f3fe97ce6e..fe6d477aa3 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -366,6 +366,7 @@ export class PlTransaction { id: localId, data: Buffer.from(name), errorIfExists, + colorProof: new Uint8Array(0), }, }, (r) => r.resourceCreateSingleton.resourceId as ResourceId, @@ -429,6 +430,7 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, + colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateStruct.resourceId, @@ -447,6 +449,7 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, + colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateEphemeral.resourceId, @@ -469,6 +472,7 @@ export class PlTransaction { id: localId, data: typeof data === "string" ? Buffer.from(data) : data, errorIfExists, + colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateValue.resourceId, @@ -518,12 +522,12 @@ export class PlTransaction { } public removeResource(rId: ResourceId): void { - this.sendVoidAsync({ oneofKind: "resourceRemove", resourceRemove: { resourceId: rId } }); + this.sendVoidAsync({ oneofKind: "resourceRemove", resourceRemove: { resourceId: rId, resourceSignature: new Uint8Array(0) } }); } public resourceExists(rId: ResourceId): Promise { return this.sendSingleAndParse( - { oneofKind: "resourceExists", resourceExists: { resourceId: rId } }, + { oneofKind: "resourceExists", resourceExists: { resourceId: rId, resourceSignature: new Uint8Array(0) } }, (r) => r.resourceExists.exists, ); } @@ -581,7 +585,7 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceGet", - resourceGet: { resourceId: toResourceId(rId), loadFields: loadFields }, + resourceGet: { resourceId: toResourceId(rId), loadFields: loadFields, resourceSignature: new Uint8Array(0) }, }, (r) => protoToResource(notEmpty(r.resourceGet.resource)), ); @@ -665,7 +669,7 @@ export class PlTransaction { this._stat.inputsLocked++; this.sendVoidAsync({ oneofKind: "resourceLockInputs", - resourceLockInputs: { resourceId: toResourceId(rId) }, + resourceLockInputs: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0) }, }); } @@ -677,7 +681,7 @@ export class PlTransaction { this._stat.outputsLocked++; this.sendVoidAsync({ oneofKind: "resourceLockOutputs", - resourceLockOutputs: { resourceId: toResourceId(rId) }, + resourceLockOutputs: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0) }, }); } @@ -689,7 +693,7 @@ export class PlTransaction { public setResourceError(rId: AnyResourceRef, ref: AnyResourceRef): void { this.sendVoidAsync({ oneofKind: "resourceSetError", - resourceSetError: { resourceId: toResourceId(rId), errorResourceId: toResourceId(ref) }, + resourceSetError: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), errorResourceId: toResourceId(ref), errorResourceSignature: new Uint8Array(0) }, }); } @@ -743,7 +747,7 @@ export class PlTransaction { this._stat.fieldsSet++; this.sendVoidAsync({ oneofKind: "fieldSetError", - fieldSetError: { field: toFieldId(fId), errorResourceId: toResourceId(ref) }, + fieldSetError: { field: toFieldId(fId), errorResourceId: toResourceId(ref), errorResourceSignature: new Uint8Array(0) }, }); } @@ -776,7 +780,7 @@ export class PlTransaction { const result = await this.sendMultiAndParse( { oneofKind: "resourceKeyValueList", - resourceKeyValueList: { resourceId: toResourceId(rId), startFrom: "", limit: 0 }, + resourceKeyValueList: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), startFrom: "", limit: 0 }, }, (r) => r.map((e) => e.resourceKeyValueList.record!), ); @@ -815,6 +819,7 @@ export class PlTransaction { oneofKind: "resourceKeyValueSet", resourceKeyValueSet: { resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), key, value: toBytes(value), }, @@ -826,6 +831,7 @@ export class PlTransaction { oneofKind: "resourceKeyValueDelete", resourceKeyValueDelete: { resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), key, }, }); @@ -836,7 +842,7 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGet", - resourceKeyValueGet: { resourceId: toResourceId(rId), key }, + resourceKeyValueGet: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), key }, }, (r) => r.resourceKeyValueGet.value, ); @@ -864,7 +870,7 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGetIfExists", - resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), key }, + resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), key }, }, (r) => r.resourceKeyValueGetIfExists.exists ? r.resourceKeyValueGetIfExists.value : undefined, From 385217bd8a67fe41d2612f275946067b654f1f9c Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 14:11:33 +0100 Subject: [PATCH 04/27] MILAB-5815: fix: format transaction.ts to pass CI formatter check --- lib/node/pl-client/src/core/transaction.ts | 48 ++++++++++++++++++---- 1 file changed, 40 insertions(+), 8 deletions(-) diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index fe6d477aa3..83e111421d 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -522,12 +522,18 @@ export class PlTransaction { } public removeResource(rId: ResourceId): void { - this.sendVoidAsync({ oneofKind: "resourceRemove", resourceRemove: { resourceId: rId, resourceSignature: new Uint8Array(0) } }); + this.sendVoidAsync({ + oneofKind: "resourceRemove", + resourceRemove: { resourceId: rId, resourceSignature: new Uint8Array(0) }, + }); } public resourceExists(rId: ResourceId): Promise { return this.sendSingleAndParse( - { oneofKind: "resourceExists", resourceExists: { resourceId: rId, resourceSignature: new Uint8Array(0) } }, + { + oneofKind: "resourceExists", + resourceExists: { resourceId: rId, resourceSignature: new Uint8Array(0) }, + }, (r) => r.resourceExists.exists, ); } @@ -585,7 +591,11 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceGet", - resourceGet: { resourceId: toResourceId(rId), loadFields: loadFields, resourceSignature: new Uint8Array(0) }, + resourceGet: { + resourceId: toResourceId(rId), + loadFields: loadFields, + resourceSignature: new Uint8Array(0), + }, }, (r) => protoToResource(notEmpty(r.resourceGet.resource)), ); @@ -693,7 +703,12 @@ export class PlTransaction { public setResourceError(rId: AnyResourceRef, ref: AnyResourceRef): void { this.sendVoidAsync({ oneofKind: "resourceSetError", - resourceSetError: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), errorResourceId: toResourceId(ref), errorResourceSignature: new Uint8Array(0) }, + resourceSetError: { + resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), + errorResourceId: toResourceId(ref), + errorResourceSignature: new Uint8Array(0), + }, }); } @@ -747,7 +762,11 @@ export class PlTransaction { this._stat.fieldsSet++; this.sendVoidAsync({ oneofKind: "fieldSetError", - fieldSetError: { field: toFieldId(fId), errorResourceId: toResourceId(ref), errorResourceSignature: new Uint8Array(0) }, + fieldSetError: { + field: toFieldId(fId), + errorResourceId: toResourceId(ref), + errorResourceSignature: new Uint8Array(0), + }, }); } @@ -780,7 +799,12 @@ export class PlTransaction { const result = await this.sendMultiAndParse( { oneofKind: "resourceKeyValueList", - resourceKeyValueList: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), startFrom: "", limit: 0 }, + resourceKeyValueList: { + resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), + startFrom: "", + limit: 0, + }, }, (r) => r.map((e) => e.resourceKeyValueList.record!), ); @@ -842,7 +866,11 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGet", - resourceKeyValueGet: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), key }, + resourceKeyValueGet: { + resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), + key, + }, }, (r) => r.resourceKeyValueGet.value, ); @@ -870,7 +898,11 @@ export class PlTransaction { const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGetIfExists", - resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0), key }, + resourceKeyValueGetIfExists: { + resourceId: toResourceId(rId), + resourceSignature: new Uint8Array(0), + key, + }, }, (r) => r.resourceKeyValueGetIfExists.exists ? r.resourceKeyValueGetIfExists.value : undefined, From 828479a9b3fd485f58d3317fa32ef32a397c3b65 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 14:59:13 +0100 Subject: [PATCH 05/27] MILAB-5815: fix: fields required by updated protobuf types --- .../pl-client/src/core/ll_transaction.test.ts | 34 ++++++++++++------- 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/node/pl-client/src/core/ll_transaction.test.ts b/lib/node/pl-client/src/core/ll_transaction.test.ts index b1e35e2a89..0c49661f9e 100644 --- a/lib/node/pl-client/src/core/ll_transaction.test.ts +++ b/lib/node/pl-client/src/core/ll_transaction.test.ts @@ -19,14 +19,14 @@ test("check successful transaction", async () => { enableFormattedErrors: false, }, }, - false, + false ); const commitResp = await tx.send( { oneofKind: "txCommit", txCommit: {}, }, - false, + false ); expect(openResp.txOpen.tx?.isValid).toBeTruthy(); @@ -48,7 +48,7 @@ test("transaction timeout test", async () => { enableFormattedErrors: false, }, }, - false, + false ); expect(response.txOpen.tx?.isValid).toBeTruthy(); await tx.await(); @@ -69,7 +69,7 @@ test("check timeout error type (passive)", async () => { enableFormattedErrors: false, }, }, - false, + false ); expect(response.txOpen.tx?.isValid).toBeTruthy(); await tx.await(); @@ -92,7 +92,7 @@ test("check timeout error type (active)", async () => { enableFormattedErrors: false, }, }, - false, + false ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); @@ -115,9 +115,10 @@ test("check timeout error type (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, + colorProof: new Uint8Array(0), }, }, - false, + false ); const id = (await createResponse).resourceCreateValue.resourceId; @@ -125,9 +126,13 @@ test("check timeout error type (active)", async () => { const vr = await tx.send( { oneofKind: "resourceGet", - resourceGet: { resourceId: id, loadFields: false }, + resourceGet: { + resourceId: id, + loadFields: false, + resourceSignature: new Uint8Array(0), + }, }, - false, + false ); expect(Buffer.compare(vr.resourceGet.resource!.data, rData)).toBe(0); @@ -151,7 +156,7 @@ test("check is abort error (active)", async () => { enableFormattedErrors: false, }, }, - false, + false ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); @@ -174,9 +179,10 @@ test("check is abort error (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, + colorProof: new Uint8Array(0), }, }, - false, + false ); const id = (await createResponse).resourceCreateValue.resourceId; @@ -184,9 +190,13 @@ test("check is abort error (active)", async () => { const vr = await tx.send( { oneofKind: "resourceGet", - resourceGet: { resourceId: id, loadFields: false }, + resourceGet: { + resourceId: id, + loadFields: false, + resourceSignature: new Uint8Array(0), + }, }, - false, + false ); expect(Buffer.compare(vr.resourceGet.resource!.data, rData)).toBe(0); From b05b7cdfd0dc733378ba0ba4f19f0148dbe0f591 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Fri, 27 Mar 2026 15:15:51 +0100 Subject: [PATCH 06/27] MILAB-5815: fix: format ll_transaction.test.ts to pass CI formatter check --- .../pl-client/src/core/ll_transaction.test.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/node/pl-client/src/core/ll_transaction.test.ts b/lib/node/pl-client/src/core/ll_transaction.test.ts index 0c49661f9e..c2a3eca09b 100644 --- a/lib/node/pl-client/src/core/ll_transaction.test.ts +++ b/lib/node/pl-client/src/core/ll_transaction.test.ts @@ -19,14 +19,14 @@ test("check successful transaction", async () => { enableFormattedErrors: false, }, }, - false + false, ); const commitResp = await tx.send( { oneofKind: "txCommit", txCommit: {}, }, - false + false, ); expect(openResp.txOpen.tx?.isValid).toBeTruthy(); @@ -48,7 +48,7 @@ test("transaction timeout test", async () => { enableFormattedErrors: false, }, }, - false + false, ); expect(response.txOpen.tx?.isValid).toBeTruthy(); await tx.await(); @@ -69,7 +69,7 @@ test("check timeout error type (passive)", async () => { enableFormattedErrors: false, }, }, - false + false, ); expect(response.txOpen.tx?.isValid).toBeTruthy(); await tx.await(); @@ -92,7 +92,7 @@ test("check timeout error type (active)", async () => { enableFormattedErrors: false, }, }, - false + false, ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); @@ -118,7 +118,7 @@ test("check timeout error type (active)", async () => { colorProof: new Uint8Array(0), }, }, - false + false, ); const id = (await createResponse).resourceCreateValue.resourceId; @@ -132,7 +132,7 @@ test("check timeout error type (active)", async () => { resourceSignature: new Uint8Array(0), }, }, - false + false, ); expect(Buffer.compare(vr.resourceGet.resource!.data, rData)).toBe(0); @@ -156,7 +156,7 @@ test("check is abort error (active)", async () => { enableFormattedErrors: false, }, }, - false + false, ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); @@ -182,7 +182,7 @@ test("check is abort error (active)", async () => { colorProof: new Uint8Array(0), }, }, - false + false, ); const id = (await createResponse).resourceCreateValue.resourceId; @@ -196,7 +196,7 @@ test("check is abort error (active)", async () => { resourceSignature: new Uint8Array(0), }, }, - false + false, ); expect(Buffer.compare(vr.resourceGet.resource!.data, rData)).toBe(0); From d47af3ac953c7893760e96d4cc5e60ea39433f49 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Mon, 6 Apr 2026 23:29:10 +0200 Subject: [PATCH 07/27] MILAB-5815: feat: proto update --- .../proto/plapi/plapiproto/api.proto | 309 +- .../proto/plapi/plapiproto/api_types.proto | 16 +- .../proto/plapi/plapiproto/base_types.proto | 1 + .../proto/plapi/plapiproto/import.proto | 4 +- .../proto/plapi/plapiproto/openapi.yaml | 2446 +++++++------ .../plapi/plapiproto/resource_types.proto | 3 +- .../proto/plapi/plapiproto/ws-test.proto | 6 +- .../pl/plapi/plapiproto/api.client.ts | 103 +- .../milaboratory/pl/plapi/plapiproto/api.ts | 1398 ++++++- .../pl/plapi/plapiproto/api_types.ts | 16 +- .../pl/plapi/plapiproto/base_types.ts | 12 + .../pl/plapi/plapiproto/import.ts | 4 +- .../pl/plapi/plapiproto/resource_types.ts | 11 + .../pl/plapi/plapiproto/ws-test.ts | 6 +- lib/node/pl-client/src/proto-rest/plapi.ts | 3215 +++++++++-------- .../proto/shared/downloadapi/openapi.yaml | 164 +- .../proto/shared/downloadapi/protocol.proto | 10 +- .../proto/shared/lsapi/openapi.yaml | 252 +- .../proto/shared/lsapi/protocol.proto | 46 +- .../proto/shared/progressapi/openapi.yaml | 236 +- .../proto/shared/progressapi/protocol.proto | 4 +- .../pl-drivers/proto/shared/protodep.toml | 4 +- .../proto/shared/streamingapi/openapi.yaml | 551 ++- .../proto/shared/streamingapi/protocol.proto | 181 +- .../proto/shared/uploadapi/openapi.yaml | 604 ++-- .../proto/shared/uploadapi/protocol.proto | 123 +- .../grpc/downloadapi/protocol.client.ts | 4 +- .../shared/grpc/downloadapi/protocol.ts | 36 +- .../shared/grpc/lsapi/protocol.client.ts | 16 +- .../controllers/shared/grpc/lsapi/protocol.ts | 50 +- .../grpc/progressapi/protocol.client.ts | 4 +- .../shared/grpc/progressapi/protocol.ts | 26 +- .../grpc/streamingapi/protocol.client.ts | 117 +- .../shared/grpc/streamingapi/protocol.ts | 338 +- .../shared/grpc/uploadapi/protocol.client.ts | 55 +- .../shared/grpc/uploadapi/protocol.ts | 335 +- .../proto-grpc/google/protobuf/descriptor.ts | 7 +- .../pl-drivers/src/proto-rest/downloadapi.ts | 177 +- lib/node/pl-drivers/src/proto-rest/lsapi.ts | 263 +- .../pl-drivers/src/proto-rest/progressapi.ts | 274 +- .../pl-drivers/src/proto-rest/streamingapi.ts | 728 ++-- .../pl-drivers/src/proto-rest/uploadapi.ts | 742 ++-- 42 files changed, 7342 insertions(+), 5555 deletions(-) diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api.proto b/lib/node/pl-client/proto/plapi/plapiproto/api.proto index b0d27c63d1..6dee23ae8f 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api.proto @@ -132,20 +132,20 @@ service Platform { // Locks // - // LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: - // - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + // LockFieldValues gets the resource and obtains a lock on all resolved values of listed fields: + // - get the resource that will take the lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) // - list resource's fields, take fields with names set in request // - get resolved values of listed fields (IDs of 'ON' resources). // - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. // // Lock logic constraints: // - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them - // succeeds, while other fails with error (no long waiting) - // - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know - // resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. - // - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before - // being lockable. Attempt to lock resource that is not became original will fail. - // - Locking is one-way operation: it cannot be 'released' or 'revoked'. + // succeeds, while the other fails with an error (no long waiting) + // - Only resolved reference can be locked: to obtain a lock for a particular field's value, the backend needs to know + // the resource ID this field points to. Unless all listed field references are resolved to a final ID, the lock will fail. + // - Only an original resource can be locked: if a resource is 'pure' (supports deduplication), it has to pass deduplication before + // being lockable. An attempt to lock a resource that has not become original will fail. + // - Locking is a one-way operation: it cannot be 'released' or 'revoked'. rpc LockFieldValues(LocksAPI.LockFieldValues.Create.Request) returns (LocksAPI.LockFieldValues.Create.Response) { option (google.api.http) = { post: "/v1/locks/lock/create" @@ -153,9 +153,9 @@ service Platform { }; } - // LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. - // Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. - // To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. + // LeaseResource creates a lease for a resource. A lease is a temporary lock that needs periodic renewal to stay valid. + // Leases are a separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + // To keep the lease active, the client needs the lease ID that is generated when a lease is created and used for lease updates. rpc LeaseResource(LocksAPI.Lease.Create.Request) returns (LocksAPI.Lease.Create.Response) { option (google.api.http) = { post: "/v1/locks/lease/create" @@ -190,6 +190,24 @@ service Platform { }; } + rpc GrantAccess(AuthAPI.GrantAccess.Request) returns (AuthAPI.GrantAccess.Response) { + option (google.api.http) = { + post: "/v1/auth/grant-access" + body: "*" + }; + } + + rpc RevokeGrant(AuthAPI.RevokeGrant.Request) returns (AuthAPI.RevokeGrant.Response) { + option (google.api.http) = { + post: "/v1/auth/revoke-grant" + body: "*" + }; + } + + rpc ListGrants(AuthAPI.ListGrants.Request) returns (stream AuthAPI.ListGrants.Response) {} + + rpc ListUserResources(AuthAPI.ListUserResources.Request) returns (stream AuthAPI.ListUserResources.Response) {} + // // Other stuff // @@ -214,22 +232,22 @@ service Platform { } } -// Platform transactions on API level are implemented as bidirectional +// Platform transactions at the API level are implemented as bidirectional // streams that exist in parallel with transactions. // One stream = one transaction at all times. // -// Long story short from client side: +// Long story short from the client side: // - client initializes stream and immediately gets transaction info // message from server; // - client calls server methods by sending messages to the server, // each message has its own request ID starting from 1, request ID number // grows as new messages are sent to the server; -// - client reads server responses for each messages sent. Client can batch +// - client reads server responses for each message sent. Client can batch // writes and reads: e.g. send 3 messages, read 2 responses, send one // more, read 2; -// - to finish communication client sends 'commit' or 'discard' message and reads last +// - to finish communication, the client sends a 'commit' or 'discard' message and reads last // response; -// - to make server interrupt communication, client sends 'stream_close' message and reads to the last +// - to make the server interrupt communication, the client sends a 'stream_close' message and reads up to the last // server response. This allows graceful stream close, waiting for all queued server responses // to be sent before closing the stream. // @@ -239,24 +257,24 @@ service Platform { // // 1. Initialization: // -// - client opens new bidirectional stream by calling 'Tx' gRPC method; -// - client sends 'tx_open' message with request_id = 0 to open RO or RW +// - the client opens a new bidirectional stream by calling 'Tx' gRPC method; +// - the client sends a 'tx_open' message with request_id = 0 to open RO or RW // transaction. -// Any message from client other than 'tx_open' during initialization is -// treated as communication error and causes server to close the stream; -// - server opens new transaction and sends transaction info to the client -// 'tx_open' server message; +// Any message from the client other than 'tx_open' during initialization is +// treated as a communication error and causes the server to close the stream; +// - the server opens a new transaction and sends transaction info to the client +// in a 'tx_open' server message; // // 2. Communication: // // Once the transaction gets initialized, all the communication between -// client and server should be considered as request-response sequence. -// The only difference to simple gRPC call is that sending a request does not -// block client until the response arrives from server. +// client and server should be considered a request-response sequence. +// The only difference from a simple gRPC call is that sending a request does not +// block the client until the response arrives from the server. // -// Client can send as many requests to the server as it wants before -// reading responses. This allows client to batch method calls, when it is not -// interested in result of each action, but needs only overall summary of +// The client can send as many requests to the server as it wants before +// reading responses. This allows the client to batch method calls, when it is not +// interested in the result of each action, but needs only the overall summary of // transaction commit. // For example, the following sequence is totally valid from client side: // 1. send reqA. @@ -266,69 +284,69 @@ service Platform { // 5. get respB. // 6. get respC. // -// Here are detailed rules of client-server communication inside transaction +// Here are the detailed rules of client-server communication inside transaction // stream: -// - if stream is closed by client or interrupted by any unrecoverable -// communication error, the accompanied transaction is discarded; -// - each message in client stream (from client to server) is considered as -// method call. -// E.g. 'resource_remove' makes server to actually remove resource inside -// transaction, bound to current stream; -// - each message in client stream MUST have its own ID (request_id), -// generated by client, UNIQUE for transaction. Numeration starts from -// 1 for each at this step. request_id == 0 is treated as unrecoverable -// communication error by server and cancels transaction; -// - server expects 'request_id' from client to grow constantly by one as +// - if the stream is closed by the client or interrupted by any unrecoverable +// communication error, the accompanying transaction is discarded; +// - each message in the client stream (from client to server) is considered +// a method call. +// E.g. 'resource_remove' makes the server actually remove the resource inside +// the transaction bound to the current stream; +// - each message in the client stream MUST have its own ID (request_id), +// generated by the client, UNIQUE for the transaction. Numbering starts from +// 1 for each new transaction. request_id == 0 is treated as an unrecoverable +// communication error by the server and cancels the transaction; +// - server expects 'request_id' from the client to grow consecutively by one as // messages come. If message M2 arrives next to M1 and // M2.request_id != (M1.request_id + 1) -// server treats it as unrecoverable communication error and cancels -// transaction; -// - all messages in server stream (from server to client) in communication -// stage have the same 'request_id' as the client messages, that triggered -// the operation. This allows client to match server responses to the +// server treats it as an unrecoverable communication error and cancels +// the transaction; +// - all messages in the server stream (from server to client) in the communication +// stage have the same 'request_id' as the client messages that triggered +// the operation. This allows the client to match server responses to the // requests it sent earlier; -// - messages order in server stream matches order in client stream: if -// client sent sequence [ReqA, ReqB], the server will always response +// - message order in the server stream matches the order in the client stream: if +// the client sent sequence [ReqA, ReqB], the server will always respond with // [RespA, RespB]; -// - server can send several responses to single client request (multi-message +// - the server can send several responses to a single client request (multi-message // response). In that case, all such responses will have the same 'request_id' -// (the one from client request), but different message IDs. See +// (the one from the client request), but different message IDs. See // 'Multi-message responses' section below for more info. -// - client is allowed (but not obligated) to generate local IDs for new +// - the client is allowed (but not obligated) to generate local IDs for new // instances and use them in links and other references. These local IDs -// are valid only within current transaction; -// - any instance in server stream always has _real_ global ID that is valid -// at any times in any other request outside current transaction; +// are valid only within the current transaction; +// - any instance in the server stream always has a _real_ global ID that is valid +// at any time in any other request outside the current transaction; // // 3. Finalization: // -// - client finalizes transaction by sending 'tx_commit' or 'tx_discard' -// message to the server. After that client can close client stream as any +// - the client finalizes the transaction by sending a 'tx_commit' or 'tx_discard' +// message to the server. After that, the client can close the client stream as any // message sent to the server after commit/discard will be ignored anyway; -// - server stops reading client stream and does the commit/discard action. -// - once transaction is closed, server sends the result to client and -// closes server stream. -// - client stream reading may be interrupted by special 'stream_close' message, which -// causes transaction automatic discard and does not produce additional reply message. +// - the server stops reading the client stream and does the commit/discard action. +// - once the transaction is closed, the server sends the result to the client and +// closes the server stream. +// - the client stream reading may be interrupted by a special 'stream_close' message, which +// causes automatic transaction discard and does not produce an additional reply message. // This 'stream_close' message also does not require any 'request_id' value and does not -// produce any response from server side. +// produce any response from the server side. // -// At this point the transaction over gRPC is considered as finalized, all +// At this point the transaction over gRPC is considered finalized, all // local IDs generated within the transaction are no longer valid. // // Multi-message responses // // Some transaction methods produce several messages by design, causing -// single call to result in multiple responses. Listings are the clear +// a single call to result in multiple responses. Listings are a clear // example of that. -// All responses from server have special field (multi_message) with all meta -// info on multi-message response: +// All responses from the server have a special field (multi_message) with all meta +// info on multi-message responses: // - for single-message responses, multi_message is always empty; // - for multi-message responses, multi_message.id is always > 0; -// - all messages in multi-message response have request_id equal to request_id from -// original client's request message; -// - last message in multi-message response always has multi_message.is_last = true; -// - empty multi-message response always has: +// - all messages in a multi-message response have request_id equal to request_id from +// the original client's request message; +// - the last message in a multi-message response always has multi_message.is_last = true; +// - an empty multi-message response always has: // multi_message.is_last = true; // multi_message.is_empty = true. // @@ -341,15 +359,15 @@ message TxAPI { Commit.Request tx_commit = 12; // commit the transaction and close the stream Discard.Request tx_discard = 13; // discard the transaction and close the stream - // Interrupt the transaction network stream on server side. + // Interrupt the transaction network stream on the server side. // - // This allows to gracefully close network connection and discard the associated transaction, + // This allows one to gracefully close the network connection and discard the associated transaction, // ensuring all pending server responses are sent to the client before closing. - // We use this in RO transactions to imitate the feature of 'half-open' connection + // We use this in RO transactions to imitate the feature of a 'half-open' connection // available in pure gRPC, when we work with WebSockets. - // Pure gRPC uses 'end of client messages stream' as a criteria for graceful discard. - // Pure WebSocket does not allow this, breaking entire connection at once. stream_close helps to get - // behaviour as we have for gRPC. + // Pure gRPC uses 'end of the client message stream' as a criterion for graceful discard. + // Pure WebSocket does not allow this, breaking the entire connection at once. stream_close helps + // achieve the same behavior as in gRPC. CloseStream.Request stream_close = 14; // ask server to send all pending messages and close the stream ResourceAPI.CreateRoot.Request resource_create_root = 58; // create new root resource @@ -365,7 +383,7 @@ message TxAPI { ResourceAPI.LockOutputs.Request resource_lock_outputs = 56; // lock outputs of resources without schema ResourceAPI.Exists.Request resource_exists = 54; // check if resource exists ResourceAPI.Get.Request resource_get = 55; // get actual resource info from server - ResourceAPI.SetError.Request resource_set_error = 61; // create a special field and set error there. + ResourceAPI.SetError.Request resource_set_error = 61; // create a special field and set an error there. ResourceAPI.List.ByType.Request resource_list_by_type = 60; // list resources of specific type ResourceAPI.Name.Set.Request resource_name_set = 66; // assign name to resource ResourceAPI.Name.Get.Request resource_name_get = 67; // get resource ID by name @@ -375,7 +393,7 @@ message TxAPI { ResourceAPI.TreeSize.Request resource_tree_size = 71; // calculate size for all resources in tree FieldAPI.Create.Request field_create = 101; // add field to resource - FieldAPI.Exists.Request field_exists = 107; // add field to resource + FieldAPI.Exists.Request field_exists = 107; // check if field exists FieldAPI.Set.Request field_set = 102; // link field to another resource or field FieldAPI.SetError.Request field_set_error = 105; // link field to error resource FieldAPI.Get.Request field_get = 103; // get field info @@ -385,12 +403,12 @@ message TxAPI { SubscriptionAPI.CreateSubscription.Request subscription_create = 111; // create subscription SubscriptionAPI.AttachFilter.Request subscription_attach_filter = 112; // add filter to existing subscription - SubscriptionAPI.DetachFilter.Request subscription_detach_filter = 113; // add filter to existing subscription + SubscriptionAPI.DetachFilter.Request subscription_detach_filter = 113; // detach filter from existing subscription SubscriptionAPI.CreateFilter.Request subscription_create_filter = 115; // create filter resource NotificationAPI.Get.Request notification_get = 154; // get notification info from platform - NotificationAPI.Ack.Request notification_ack = 155; // acknowledge notification handle by controller - NotificationAPI.Discard.Request notification_discard = 156; // discard notification handle by controller + NotificationAPI.Ack.Request notification_ack = 155; // acknowledge notification handled by controller + NotificationAPI.Discard.Request notification_discard = 156; // discard notification handled by controller ResourceKVAPI.Set.Request resource_key_value_set = 200; // set a value to a resource's key-value store. ResourceKVAPI.Get.Request resource_key_value_get = 201; // get a value from a resource's key-value store. @@ -427,7 +445,7 @@ message TxAPI { // Sequential message ID for multi-message response, starting from 1. // Caller can use 'id > 0' check as a sign of multi-message response. // Some API requests produce several messages in response by design (say, listings) - // In that case, the server responses to the client with many messages, each having + // In that case, the server responds to the client with many messages, each having // the same value and different values. uint32 id = 1; @@ -436,7 +454,7 @@ message TxAPI { bool is_last = 2; // Sign of empty multi-message response. Some multi-message responses can produce nothing - // (like listing of empty directory). In that case client still has to know that the request was + // (like a listing of an empty directory). In that case, the client still has to know that the request was // handled and the empty result is OK. bool is_empty = 3; } @@ -553,7 +571,7 @@ message TxAPI { message CloseStream { message Request {} - message Response {} // is never sent. Simplifies dark magic of typing on TypeScript side. + message Response {} // is never sent. Simplifies the dark magic of typing on the TypeScript side. } message Sync { @@ -732,16 +750,16 @@ message ResourceAPI { message Request { Base.ResourceType resource_type = 1; - // Non-zero value makes API to limit its responses count to at most + // Non-zero value makes the API limit its response count to at most // number of items. - // Zero value makes API to return all available items. + // Zero value makes the API return all available items. uint32 limit = 3; // Start listing from given resource ID (not including this resource ID itself) // After == 100 means listing will NOT contain resources with ID = 100 and lower. uint64 after = 6; - // True value makes API to return original resources instead of duplicates. + // True value makes the API return original resources instead of duplicates. bool resolve_duplicates = 4; // Load fields for each resource in list. @@ -769,7 +787,7 @@ message ResourceAPI { // FIXME: add CreateResource method to API - // Remove any resource, that has garbage collection disabled + // Remove any resource that has garbage collection disabled message Remove { message Request { uint64 resource_id = 1; @@ -783,6 +801,7 @@ message ResourceAPI { message Set { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; string name = 2; } @@ -824,11 +843,11 @@ message ResourceAPI { uint64 resource_id = 1; bytes resource_signature = 3; - // Limit maximum depth the tree is traversed. - // The resource is considered on depth = 0, the values of its fiels + // Limit the maximum depth the tree is traversed. + // The resource is considered at depth = 0, the values of its fields // are on depth = 1 and so on. - // The maximum uint32 value disables the limit at all. - // 0 value makes API to return only single resource and is actually + // The maximum uint32 value disables the limit entirely. + // A 0 value makes the API return only a single resource and is actually // equal to Get.Request optional uint32 max_depth = 2; } @@ -846,7 +865,7 @@ message ResourceAPI { } message Response { // size of all tree resources in bytes - // could change between call regarding compression algorithm + // could change between calls depending on compression algorithm uint64 size = 1; uint64 resource_count = 2; } @@ -929,9 +948,9 @@ message FieldAPI { // name >= start_from. string start_from = 2; - // Non-zero value makes API to limit number of returned fields to at + // Non-zero value makes the API limit the number of returned fields to at // most . - // Zero value makes API to return all available fields of the resource. + // Zero value makes the API return all available fields of the resource. uint32 limit = 3; } @@ -942,7 +961,7 @@ message FieldAPI { // The name of field next to the current one. // // Is not empty only for the last message in the current listing, when was > 0 in List.Request AND - // there is more items to read (the listing was stopped because of that limit) + // there are more items to read (the listing was stopped because of that limit) // // Use value as in the List.Request to continue listing. string next = 4; @@ -1183,9 +1202,9 @@ message ResourceKVAPI { // key >= start_from. string start_from = 2; - // Non-zero value makes API to limit its responses count to at most + // Non-zero value makes the API limit its response count to at most // number of items. - // Zero value makes API to return all available items. + // Zero value makes the API return all available items. uint32 limit = 3; } @@ -1201,7 +1220,7 @@ message ResourceKVAPI { // The key of the KV item next to the last returned item. // // Is not empty only for the last message in the current listing, when was > 0 in List.Request AND - // there is more items to read (the listing was stopped because of that limit) + // there are more items to read (the listing was stopped because of that limit) // // Use value as of the List.Request to continue listing. string next = 4; @@ -1368,13 +1387,13 @@ message LocksAPI { message Create { message Request { uint64 resource_id = 1; // resource ID that will own the lock - repeated string lock_references_of = 2; // list of fields, which values should be locked + repeated string lock_references_of = 2; // list of fields whose values should be locked string comment = 3; // comment to show for other lockers on lock conflict. Truncated to 1024 bytes. } message Response { // true when lock was acquired (new, or already owned by the owner) - // Client MUST pay attention to this flag, as it shows if lock was successfull. + // Client MUST pay attention to this flag, as it shows if lock was successful. bool acquired = 1; // Info about why lock was not acquired. @@ -1466,6 +1485,93 @@ message AuthAPI { bytes session_id = 2; } } + + message GrantAccess { + message Request { + uint64 resource_id = 1; + bytes resource_signature = 2; + string target_user = 3; // user login to grant access to + Grant.Permissions permissions = 4; // access level to grant + } + + message Response {} + } + + message RevokeGrant { + message Request { + uint64 resource_id = 1; + bytes resource_signature = 2; + string target_user = 3; // user login whose grant to revoke + } + + message Response {} + } + + message ListGrants { + message Request { + uint64 resource_id = 1; + bytes resource_signature = 2; + } + + message Response { + Grant grant = 1; // one per stream message + } + } + + message Grant { + // Permissions describes access level for a grant. + message Permissions { + bool writable = 1; // write access = ability to modify resource tree + share access + } + + string user = 1; + uint64 resource_id = 2; + Permissions permissions = 3; // access level from grant table + string granted_by = 4; // login of the user who created this grant + int64 granted_at = 5; // unix timestamp in milliseconds + } + + message ListUserResources { + message Request { + // User login to list resources for. + // Is ignored for regular user role: it can list only its own resources. + string login = 1; + + // Start listing shared resources from given resource ID. + // User root is returned only on the first page (when start_from == 0). + uint64 start_from = 2; + + // Non-zero value limits total resource count to at most . + // limit = 1 with start_from = 0 returns solely user's root + // limit = 2 with non-zero start_from returns 2 shared resources. + // Zero returns all resources. + uint32 limit = 3; + } + + // Multi-message: first message contains UserRoot (only when start_from == 0), + message Response { + oneof entry { + UserRoot user_root = 1; + SharedResource shared_resource = 2; + } + + // Set only on the last message when limit > 0 and more items exist. + // Use as 'start_from' in the next request to continue listing. + uint64 next_resource_id = 3; + } + + message UserRoot { + uint64 resource_id = 1; + bytes resource_signature = 2; + } + + message SharedResource { + uint64 resource_id = 1; + bytes resource_signature = 2; + Base.ResourceType resource_type = 3; + Grant.Permissions permissions = 4; + } + } } message MiscAPI { @@ -1492,7 +1598,6 @@ message MaintenanceAPI { string core_version = 1; string core_full_version = 2; - string server_info = 3; Compression compression = 4; // instanceID is a unique ID that changes when we reset DB state. @@ -1504,6 +1609,8 @@ message MaintenanceAPI { string platform = 6; // - string os = 7; // linux / windows / macosx string arch = 8; // x64 / aarch64 + + // next free: 9 } } diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto index 997ca75a7b..80a10b7678 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto @@ -22,7 +22,7 @@ message Resource { uint64 resource_id = 2; bytes resource_signature = 18; - bytes canonical_id = 17; // could be empty, it depends on resource lifecycle state + bytes canonical_id = 17; // could be empty; it depends on the resource lifecycle state Kind kind = 3; Base.ResourceType type = 4; bytes data = 5; @@ -57,10 +57,10 @@ message Field { Base.FieldType type = 2; Resource.Features features = 3; - // _resolved_ value of field or _assigned_ if the field was assigned to a resource. - // If field refers to another field, it will get - // value only when this chain of references ends up with direct resource - // reference. At that moment all fields in the chain will get their values + // _resolved_ value of a field or _assigned_ if the field was assigned to a resource. + // If a field refers to another field, it will get + // a value only when this chain of references ends up with a direct resource + // reference. At that moment, all fields in the chain will get their values // resolved and will start to refer to the same resource directly. uint64 value = 5; // Signature for value resource ID, inheriting the parent resource's color. @@ -74,14 +74,14 @@ message Field { RESOLVED = 3; } - // If the value was empty, assigned or finally resolved. + // Whether the value is empty, assigned, or finally resolved. ValueStatus value_status = 7; // If the value is in its final state (ready, duplicate or error) bool value_is_final = 8; - // Resource error resource id if any. - // Is intended to report problems _from_ platform to client. + // Error resource ID, if any. + // Is intended to report problems _from_ the platform to the client. uint64 error = 6; // Signature for error resource ID, inheriting the parent resource's color. bytes error_signature = 11; diff --git a/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto index 15e08617ec..d34210c432 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto @@ -23,5 +23,6 @@ enum FieldType { message FieldRef { uint64 resource_id = 2; + bytes resource_signature = 4; string field_name = 3; } diff --git a/lib/node/pl-client/proto/plapi/plapiproto/import.proto b/lib/node/pl-client/proto/plapi/plapiproto/import.proto index 55b41a2647..77057a5de4 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/import.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/import.proto @@ -11,8 +11,8 @@ import "google/protobuf/struct.proto"; import "google/protobuf/timestamp.proto"; import "google/protobuf/wrappers.proto"; -// Makes protobuf to forcefully compile all well-known types into code (for TS) -// or just add dependencies on them (for go) +// Forces protobuf to compile all well-known types into code (for TS) +// or just adds dependencies on them (for go) message _ImportWellKnown { google.protobuf.Empty empty = 1; google.protobuf.Any any = 2; diff --git a/lib/node/pl-client/proto/plapi/plapiproto/openapi.yaml b/lib/node/pl-client/proto/plapi/plapiproto/openapi.yaml index 9e65dbcb1b..b1bd1ff9f4 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/openapi.yaml +++ b/lib/node/pl-client/proto/plapi/plapiproto/openapi.yaml @@ -3,1131 +3,1327 @@ openapi: 3.0.3 info: - title: Platform API - version: 1.0.0 + title: Platform API + version: 1.0.0 paths: - /v1/auth/jwt-token: - post: - tags: - - Platform - operationId: Platform_GetJWTToken - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetJWTToken_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetJWTToken_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/auth/methods: - get: - tags: - - Platform - description: Authentication - operationId: Platform_AuthMethods - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListMethods_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/aliases-and-urls: - post: - tags: - - Platform - operationId: Platform_WriteControllerAliasesAndUrls - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/WriteAliasesAndUrls_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/WriteAliasesAndUrls_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - delete: - tags: - - Platform - operationId: Platform_RemoveControllerAliasesAndUrls - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RemoveAliasesAndUrls_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/RemoveAliasesAndUrls_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/attach-subscription: - post: - tags: - - Platform - operationId: Platform_ControllerAttachSubscription - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/AttachSubscription_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/AttachSubscription_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/create: - post: - tags: - - Platform - operationId: Platform_ControllerCreate - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Create_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Create_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/deregister: - post: - tags: - - Platform - operationId: Platform_ControllerDeregister - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Deregister_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Deregister_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/exists: - post: - tags: - - Platform - operationId: Platform_ControllerExists - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Exists_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Exists_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/features: - post: - tags: - - Platform - operationId: Platform_ControllerSetFeatures - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/SetFeatures_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/SetFeatures_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - delete: - tags: - - Platform - operationId: Platform_ControllerClearFeatures - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/ClearFeatures_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ClearFeatures_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/get: - post: - tags: - - Platform - operationId: Platform_ControllerGet - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Get_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Get_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/notifications: - post: - tags: - - Platform - operationId: Platform_GetControllerNotifications - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetNotifications_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetNotifications_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/register: - post: - tags: - - Platform - description: Controllers - operationId: Platform_ControllerRegister - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Register_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Register_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/update: - post: - tags: - - Platform - operationId: Platform_ControllerUpdate - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Update_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Update_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/controller/url: - post: - tags: - - Platform - operationId: Platform_GetControllerUrl - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetUrl_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetUrl_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/license: - get: - tags: - - Platform - operationId: Platform_License - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/License_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/locks/lease: - put: - tags: - - Platform - operationId: Platform_UpdateLease - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Update_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Update_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - post: - tags: - - Platform - description: Locks - operationId: Platform_LeaseResource - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Create_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Create_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - delete: - tags: - - Platform - operationId: Platform_ReleaseLease - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Release_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Release_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/notifications/get: - post: - tags: - - Platform - operationId: Platform_NotificationsGet - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Get_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Get_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/ping: - get: - tags: - - Platform - description: Various service requests - operationId: Platform_Ping - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Ping_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/resource-types: - get: - tags: - - Platform - description: Other stuff - operationId: Platform_ListResourceTypes - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/ListResourceTypes_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/subscription/attach-filter: - post: - tags: - - Platform - description: Subscriptions - operationId: Platform_SubscriptionAttachFilter - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/AttachFilter_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/AttachFilter_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/subscription/detach-filter: - post: - tags: - - Platform - operationId: Platform_SubscriptionDetachFilter - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/DetachFilter_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/DetachFilter_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/tx-sync: - post: - tags: - - Platform - operationId: Platform_TxSync - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Sync_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Sync_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/auth/grant-access: + post: + tags: + - Platform + operationId: Platform_GrantAccess + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GrantAccess_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GrantAccess_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/auth/jwt-token: + post: + tags: + - Platform + operationId: Platform_GetJWTToken + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetJWTToken_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetJWTToken_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/auth/methods: + get: + tags: + - Platform + description: Authentication + operationId: Platform_AuthMethods + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListMethods_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/auth/revoke-grant: + post: + tags: + - Platform + operationId: Platform_RevokeGrant + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RevokeGrant_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RevokeGrant_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/aliases-and-urls: + post: + tags: + - Platform + operationId: Platform_WriteControllerAliasesAndUrls + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/WriteAliasesAndUrls_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/WriteAliasesAndUrls_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - Platform + operationId: Platform_RemoveControllerAliasesAndUrls + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAliasesAndUrls_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RemoveAliasesAndUrls_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/attach-subscription: + post: + tags: + - Platform + operationId: Platform_ControllerAttachSubscription + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AttachSubscription_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AttachSubscription_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/create: + post: + tags: + - Platform + operationId: Platform_ControllerCreate + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/deregister: + post: + tags: + - Platform + operationId: Platform_ControllerDeregister + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Deregister_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Deregister_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/exists: + post: + tags: + - Platform + operationId: Platform_ControllerExists + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Exists_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Exists_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/features: + post: + tags: + - Platform + operationId: Platform_ControllerSetFeatures + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/SetFeatures_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/SetFeatures_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + delete: + tags: + - Platform + operationId: Platform_ControllerClearFeatures + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/ClearFeatures_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ClearFeatures_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/get: + post: + tags: + - Platform + operationId: Platform_ControllerGet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Get_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Get_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/notifications: + post: + tags: + - Platform + operationId: Platform_GetControllerNotifications + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetNotifications_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetNotifications_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/register: + post: + tags: + - Platform + description: Controllers + operationId: Platform_ControllerRegister + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Register_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Register_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/update: + post: + tags: + - Platform + operationId: Platform_ControllerUpdate + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Update_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Update_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/controller/url: + post: + tags: + - Platform + operationId: Platform_GetControllerUrl + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetUrl_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetUrl_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/license: + get: + tags: + - Platform + operationId: Platform_License + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/License_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/locks/lease/create: + post: + tags: + - Platform + description: |- + LeaseResource creates a lease for a resource. A lease is a temporary lock that needs periodic renewal to stay valid. + Leases are a separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + To keep the lease active, the client needs the lease ID that is generated when a lease is created and used for lease updates. + operationId: Platform_LeaseResource + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/locks/lease/release: + post: + tags: + - Platform + operationId: Platform_ReleaseLease + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Release_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Release_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/locks/lease/update: + post: + tags: + - Platform + operationId: Platform_UpdateLease + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Update_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Update_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/locks/lock/create: + post: + tags: + - Platform + description: |- + LockFieldValues gets the resource and obtains a lock on all resolved values of listed fields: + - get the resource that will take the lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + - list resource's fields, take fields with names set in request + - get resolved values of listed fields (IDs of 'ON' resources). + - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. + + Lock logic constraints: + - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them + succeeds, while the other fails with an error (no long waiting) + - Only resolved reference can be locked: to obtain a lock for a particular field's value, the backend needs to know + the resource ID this field points to. Unless all listed field references are resolved to a final ID, the lock will fail. + - Only an original resource can be locked: if a resource is 'pure' (supports deduplication), it has to pass deduplication before + being lockable. An attempt to lock a resource that has not become original will fail. + - Locking is a one-way operation: it cannot be 'released' or 'revoked'. + operationId: Platform_LockFieldValues + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Create_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/notifications/get: + post: + tags: + - Platform + operationId: Platform_NotificationsGet + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Get_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Get_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/ping: + get: + tags: + - Platform + description: Various service requests + operationId: Platform_Ping + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Ping_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/resource-types: + get: + tags: + - Platform + description: Other stuff + operationId: Platform_ListResourceTypes + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/ListResourceTypes_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/subscription/attach-filter: + post: + tags: + - Platform + description: Subscriptions + operationId: Platform_SubscriptionAttachFilter + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/AttachFilter_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/AttachFilter_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/subscription/detach-filter: + post: + tags: + - Platform + operationId: Platform_SubscriptionDetachFilter + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/DetachFilter_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/DetachFilter_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/tx-sync: + post: + tags: + - Platform + operationId: Platform_TxSync + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Sync_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Sync_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - AttachFilter_Request: - type: object - properties: - subscriptionId: - type: string - filterName: - type: string - filterId: - type: string - AttachFilter_Response: - type: object - properties: {} - AttachSubscription_Request: - type: object - properties: - controllerId: - type: string - subscriptionId: - type: string - AttachSubscription_Response: - type: object - properties: {} - ClearFeatures_Request: - type: object - properties: - controllerType: - type: string - ClearFeatures_Response: - type: object - properties: {} - Create_Request: - type: object - properties: - type: - type: integer - format: enum - id: - allOf: - - $ref: "#/components/schemas/FieldRef" - description: field ID is always combination of parent resource ID and field name - Create_Response: - type: object - properties: - globalId: - $ref: "#/components/schemas/FieldRef" - Deregister_Request: - type: object - properties: - controllerType: - type: string - Deregister_Response: - type: object - properties: {} - DetachFilter_Request: - type: object - properties: - subscriptionId: - type: string - filterName: - type: string - DetachFilter_Response: - type: object - properties: {} - Exists_Request: - type: object - properties: - resourceId: - type: string - Exists_Response: - type: object - properties: - exists: - type: boolean - Field: - type: object - properties: - id: - allOf: - - $ref: "#/components/schemas/FieldRef" - description: field ID is always combination of parent resource ID and field name - type: - type: integer - format: enum - features: - $ref: "#/components/schemas/Resource_Features" - value: - type: string - description: |- - _resolved_ value of field or _assigned_ if the field was assigned to a resource. - If field refers to another field, it will get - value only when this chain of references ends up with direct resource - reference. At that moment all fields in the chain will get their values - resolved and will start to refer to the same resource directly. - valueStatus: - type: integer - description: If the value was empty, assigned or finally resolved. - format: enum - valueIsFinal: - type: boolean - description: If the value is in its final state (ready, duplicate or error) - error: - type: string - description: |- - Resource error resource id if any. - Is intended to report problems _from_ platform to client. - FieldRef: - type: object - properties: - resourceId: - type: string - fieldName: - type: string - FieldSchema: - type: object - properties: - type: - type: integer - format: enum - name: - type: string - GetJWTToken_Request: - type: object - properties: - expiration: - pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ - type: string - GetJWTToken_Response: - type: object - properties: - token: - type: string - GetNotifications_Request: - type: object - properties: - controllerType: - type: string - maxNotifications: - type: integer - format: uint32 - GetNotifications_Response: - type: object - properties: - notifications: - type: array - items: - $ref: "#/components/schemas/Notification" - GetUrl_Request: - type: object - properties: - controllerAlias: - type: string - resourceId: - type: string - GetUrl_Response: - type: object - properties: - controllerUrl: - type: string - Get_Request: - type: object - properties: - resourceId: - type: string - loadFields: - type: boolean - Get_Response: - type: object - properties: - resource: - $ref: "#/components/schemas/Resource" - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - License_Response: - type: object - properties: - status: - type: integer - format: int32 - isOk: - type: boolean - responseBody: - type: string - description: Raw response body as it was received from the license server. - format: bytes - ListMethods_MethodInfo: - type: object - properties: - type: - type: string - name: - type: string - info: - type: object - additionalProperties: - type: string - ListMethods_Response: - type: object - properties: - methods: - type: array - items: - $ref: "#/components/schemas/ListMethods_MethodInfo" - ListResourceTypes_Response: - type: object - properties: - types: - type: array - items: - $ref: "#/components/schemas/ResourceType" - Notification: - type: object - properties: - subscriptionId: - type: string - eventId: - type: string - resourceId: - type: string - resourceType: - $ref: "#/components/schemas/ResourceType" - events: - $ref: "#/components/schemas/Notification_Events" - fieldChanges: - type: object - additionalProperties: - $ref: "#/components/schemas/Notification_FieldChange" - payload: - $ref: "#/components/schemas/NotificationFilter_Payload" - filterName: - type: string - txSpan: - $ref: "#/components/schemas/SpanInfo" - NotificationFilter: - type: object - properties: - resourceType: - $ref: "#/components/schemas/ResourceType" - resourceId: - type: string - eventFilter: - $ref: "#/components/schemas/NotificationFilter_EventFilter" - payload: - $ref: "#/components/schemas/NotificationFilter_Payload" - NotificationFilter_EventFilter: - type: object - properties: - all: - type: boolean - resourceCreated: - type: boolean - resourceDeleted: - type: boolean - resourceReady: - type: boolean - resourceDuplicate: - type: boolean - resourceError: - type: boolean - inputsLocked: - type: boolean - description: Field events - outputsLocked: - type: boolean - fieldCreated: - type: boolean - fieldGotError: - type: boolean - inputSet: - type: boolean - allInputsSet: - type: boolean - outputSet: - type: boolean - allOutputsSet: - type: boolean - genericOtwSet: - type: boolean - dynamicChanged: - type: boolean - NotificationFilter_Payload: - type: object - properties: - values: - type: object - additionalProperties: - type: string - format: bytes - Notification_Events: - type: object - properties: - resourceCreated: - type: boolean - resourceDeleted: - type: boolean - resourceReady: - type: boolean - resourceDuplicate: - type: boolean - resourceError: - type: boolean - inputsLocked: - type: boolean - outputsLocked: - type: boolean - fieldCreated: - type: boolean - fieldGotError: - type: boolean - inputSet: - type: boolean - allInputsSet: - type: boolean - outputSet: - type: boolean - allOutputsSet: - type: boolean - genericOtwSet: - type: boolean - dynamicChanged: - type: boolean - Notification_FieldChange: - type: object - properties: - old: - $ref: "#/components/schemas/Field" - new: - $ref: "#/components/schemas/Field" - Ping_Response: - type: object - properties: - coreVersion: - type: string - coreFullVersion: - type: string - serverInfo: - type: string - compression: - type: integer - format: enum - instanceId: - type: string - description: |- - instanceID is a unique ID that changes when we reset DB state. - If we reset a state and a database, but the address of the backend is still the same, - without instanceID we are not sure if it's the same state or not, - and UI can't detect it and clear its state (e.g. caches of drivers). - platform: - type: string - os: - type: string - arch: - type: string - Register_Request: - type: object - properties: - controllerType: - type: string - filters: - type: object - additionalProperties: - $ref: "#/components/schemas/NotificationFilter" - resourceSchemas: - type: array - items: - $ref: "#/components/schemas/ResourceSchema" - Register_Response: - type: object - properties: - controllerId: - type: string - subscriptionId: - type: string - Release_Request: - type: object - properties: - resourceId: - type: string - leaseId: - type: string - format: bytes - Release_Response: - type: object - properties: {} - RemoveAliasesAndUrls_Request: - type: object - properties: - controllerType: - type: string - RemoveAliasesAndUrls_Response: - type: object - properties: {} - Resource: - type: object - properties: - id: - type: string - canonicalId: - type: string - format: bytes - kind: - type: integer - format: enum - type: - $ref: "#/components/schemas/ResourceType" - data: - type: string - format: bytes - features: - $ref: "#/components/schemas/Resource_Features" - fields: - type: array - items: - $ref: "#/components/schemas/Field" - hasErrors: - type: boolean - description: Resource has at least one field with error - inputsLocked: - type: boolean - outputsLocked: - type: boolean - resourceReady: - type: boolean - isFinal: - type: boolean - originalResourceId: - type: string - parentResourceId: - type: string - createdTime: - type: string - format: date-time - deletedTime: - type: string - format: date-time - ResourceAPIFeature: - type: object - properties: - controllerType: - type: string - featureName: - type: string - resourceType: - $ref: "#/components/schemas/ResourceType" - endpoint: - type: string - ResourceSchema: - type: object - properties: - type: - $ref: "#/components/schemas/ResourceType" - fields: - type: array - items: - $ref: "#/components/schemas/FieldSchema" - ResourceType: - type: object - properties: - name: - type: string - version: - type: string - Resource_Features: - type: object - properties: - ephemeral: - type: boolean - SetFeatures_Request: - type: object - properties: - features: - type: array - items: - $ref: "#/components/schemas/ResourceAPIFeature" - SetFeatures_Response: - type: object - properties: {} - SpanInfo: - type: object - properties: - path: - type: string - carrier: - type: object - additionalProperties: - type: string - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." - Sync_Request: - type: object - properties: - txId: - type: string - Sync_Response: - type: object - properties: {} - Update_Request: - type: object - properties: - controllerType: - type: string - filters: - type: object - additionalProperties: - $ref: "#/components/schemas/NotificationFilter" - resourceSchemas: - type: array - items: - $ref: "#/components/schemas/ResourceSchema" - Update_Response: - type: object - properties: {} - WriteAliasesAndUrls_Request: - type: object - properties: - controllerType: - type: string - aliasesToUrls: - type: object - additionalProperties: - type: string - WriteAliasesAndUrls_Response: - type: object - properties: {} + schemas: + AttachFilter_Request: + type: object + properties: + subscriptionId: + type: string + filterName: + type: string + filterId: + type: string + AttachFilter_Response: + type: object + properties: {} + AttachSubscription_Request: + type: object + properties: + controllerId: + type: string + subscriptionId: + type: string + AttachSubscription_Response: + type: object + properties: {} + ClearFeatures_Request: + type: object + properties: + controllerType: + type: string + ClearFeatures_Response: + type: object + properties: {} + Create_Request: + type: object + properties: + type: + type: integer + format: enum + id: + allOf: + - $ref: '#/components/schemas/FieldRef' + description: field ID is always combination of parent resource ID and field name + Create_Response: + type: object + properties: + globalId: + $ref: '#/components/schemas/FieldRef' + Deregister_Request: + type: object + properties: + controllerType: + type: string + Deregister_Response: + type: object + properties: {} + DetachFilter_Request: + type: object + properties: + subscriptionId: + type: string + filterName: + type: string + DetachFilter_Response: + type: object + properties: {} + Exists_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + Exists_Response: + type: object + properties: + exists: + type: boolean + Field: + type: object + properties: + id: + allOf: + - $ref: '#/components/schemas/FieldRef' + description: field ID is always combination of parent resource ID and field name + type: + type: integer + format: enum + features: + $ref: '#/components/schemas/Resource_Features' + value: + type: string + description: |- + _resolved_ value of a field or _assigned_ if the field was assigned to a resource. + If a field refers to another field, it will get + a value only when this chain of references ends up with a direct resource + reference. At that moment, all fields in the chain will get their values + resolved and will start to refer to the same resource directly. + valueSignature: + type: string + description: |- + Signature for value resource ID, inheriting the parent resource's color. + Populated server-side when the parent resource has a known color in the current TX. + format: bytes + valueStatus: + type: integer + description: Whether the value is empty, assigned, or finally resolved. + format: enum + valueIsFinal: + type: boolean + description: If the value is in its final state (ready, duplicate or error) + error: + type: string + description: |- + Error resource ID, if any. + Is intended to report problems _from_ the platform to the client. + errorSignature: + type: string + description: Signature for error resource ID, inheriting the parent resource's color. + format: bytes + FieldRef: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + fieldName: + type: string + FieldSchema: + type: object + properties: + type: + type: integer + format: enum + name: + type: string + GetJWTToken_Request: + type: object + properties: + expiration: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + requestedRole: + type: integer + format: enum + GetJWTToken_Response: + type: object + properties: + token: + type: string + sessionId: + type: string + format: bytes + GetNotifications_Request: + type: object + properties: + controllerType: + type: string + maxNotifications: + type: integer + format: uint32 + GetNotifications_Response: + type: object + properties: + notifications: + type: array + items: + $ref: '#/components/schemas/Notification' + GetUrl_Request: + type: object + properties: + controllerAlias: + type: string + resourceId: + type: string + GetUrl_Response: + type: object + properties: + controllerUrl: + type: string + Get_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + loadFields: + type: boolean + Get_Response: + type: object + properties: + resource: + $ref: '#/components/schemas/Resource' + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + GrantAccess_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + targetUser: + type: string + permissions: + $ref: '#/components/schemas/Grant_Permissions' + GrantAccess_Response: + type: object + properties: {} + Grant_Permissions: + type: object + properties: + writable: + type: boolean + description: Permissions describes access level for a grant. + License_Response: + type: object + properties: + status: + type: integer + format: int32 + isOk: + type: boolean + responseBody: + type: string + description: Raw response body as it was received from the license server. + format: bytes + ListMethods_MethodInfo: + type: object + properties: + type: + type: string + name: + type: string + info: + type: object + additionalProperties: + type: string + ListMethods_Response: + type: object + properties: + methods: + type: array + items: + $ref: '#/components/schemas/ListMethods_MethodInfo' + ListResourceTypes_Response: + type: object + properties: + types: + type: array + items: + $ref: '#/components/schemas/ResourceType' + Notification: + type: object + properties: + subscriptionId: + type: string + eventId: + type: string + resourceId: + type: string + resourceType: + $ref: '#/components/schemas/ResourceType' + events: + $ref: '#/components/schemas/Notification_Events' + fieldChanges: + type: object + additionalProperties: + $ref: '#/components/schemas/Notification_FieldChange' + payload: + $ref: '#/components/schemas/NotificationFilter_Payload' + filterName: + type: string + txSpan: + $ref: '#/components/schemas/SpanInfo' + NotificationFilter: + type: object + properties: + resourceType: + $ref: '#/components/schemas/ResourceType' + resourceId: + type: string + eventFilter: + $ref: '#/components/schemas/NotificationFilter_EventFilter' + payload: + $ref: '#/components/schemas/NotificationFilter_Payload' + NotificationFilter_EventFilter: + type: object + properties: + all: + type: boolean + resourceCreated: + type: boolean + resourceDeleted: + type: boolean + resourceReady: + type: boolean + resourceRecovered: + type: boolean + resourceDuplicate: + type: boolean + resourceError: + type: boolean + inputsLocked: + type: boolean + description: Field events + outputsLocked: + type: boolean + fieldCreated: + type: boolean + fieldGotError: + type: boolean + inputSet: + type: boolean + allInputsSet: + type: boolean + outputSet: + type: boolean + allOutputsSet: + type: boolean + genericOtwSet: + type: boolean + dynamicChanged: + type: boolean + NotificationFilter_Payload: + type: object + properties: + values: + type: object + additionalProperties: + type: string + format: bytes + Notification_Events: + type: object + properties: + resourceCreated: + type: boolean + resourceDeleted: + type: boolean + resourceReady: + type: boolean + resourceDuplicate: + type: boolean + resourceError: + type: boolean + inputsLocked: + type: boolean + outputsLocked: + type: boolean + fieldCreated: + type: boolean + fieldGotError: + type: boolean + inputSet: + type: boolean + allInputsSet: + type: boolean + outputSet: + type: boolean + allOutputsSet: + type: boolean + genericOtwSet: + type: boolean + dynamicChanged: + type: boolean + resourceRecovered: + type: boolean + Notification_FieldChange: + type: object + properties: + old: + $ref: '#/components/schemas/Field' + new: + $ref: '#/components/schemas/Field' + Ping_Response: + type: object + properties: + coreVersion: + type: string + coreFullVersion: + type: string + compression: + type: integer + format: enum + instanceId: + type: string + description: |- + instanceID is a unique ID that changes when we reset DB state. + If we reset a state and a database, but the address of the backend is still the same, + without instanceID we are not sure if it's the same state or not, + and UI can't detect it and clear its state (e.g. caches of drivers). + platform: + type: string + os: + type: string + arch: + type: string + Register_Request: + type: object + properties: + controllerType: + type: string + filters: + type: object + additionalProperties: + $ref: '#/components/schemas/NotificationFilter' + resourceSchemas: + type: array + items: + $ref: '#/components/schemas/ResourceSchema' + Register_Response: + type: object + properties: + controllerId: + type: string + subscriptionId: + type: string + Release_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + leaseId: + type: string + format: bytes + Release_Response: + type: object + properties: {} + RemoveAliasesAndUrls_Request: + type: object + properties: + controllerType: + type: string + RemoveAliasesAndUrls_Response: + type: object + properties: {} + Resource: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + canonicalId: + type: string + format: bytes + kind: + type: integer + format: enum + type: + $ref: '#/components/schemas/ResourceType' + data: + type: string + format: bytes + features: + $ref: '#/components/schemas/Resource_Features' + fields: + type: array + items: + $ref: '#/components/schemas/Field' + hasErrors: + type: boolean + description: Resource has at least one field with error + inputsLocked: + type: boolean + outputsLocked: + type: boolean + resourceReady: + type: boolean + isFinal: + type: boolean + originalResourceId: + type: string + parentResourceId: + type: string + createdTime: + type: string + format: date-time + deletedTime: + type: string + format: date-time + ResourceAPIFeature: + type: object + properties: + controllerType: + type: string + featureName: + type: string + resourceType: + $ref: '#/components/schemas/ResourceType' + endpoint: + type: string + ResourceSchema: + type: object + properties: + type: + $ref: '#/components/schemas/ResourceType' + fields: + type: array + items: + $ref: '#/components/schemas/FieldSchema' + accessFlags: + allOf: + - $ref: '#/components/schemas/ResourceSchema_AccessFlags' + description: Access restriction flags for non-controller roles + freeInputs: + type: boolean + freeOutputs: + type: boolean + ResourceSchema_AccessFlags: + type: object + properties: + createResource: + type: boolean + description: |- + Deny-list approach: default = allowed (true) + Controllers set these to false to restrict non-controller roles (role='u', role='w') + readFields: + type: boolean + description: 'IMPORTANT: read_fields=false with write_fields=true is a forbidden combination' + writeFields: + type: boolean + readKv: + type: boolean + description: 'IMPORTANT: read_kv=false with write_kv=true is a forbidden combination' + writeKv: + type: boolean + readByFieldType: + type: object + additionalProperties: + type: boolean + description: |- + Per-field-type overrides (map: field_type → bool) + When defined for a field type, overrides resource-level flags + writeByFieldType: + type: object + additionalProperties: + type: boolean + ResourceType: + type: object + properties: + name: + type: string + version: + type: string + Resource_Features: + type: object + properties: + ephemeral: + type: boolean + RevokeGrant_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + targetUser: + type: string + RevokeGrant_Response: + type: object + properties: {} + SetFeatures_Request: + type: object + properties: + features: + type: array + items: + $ref: '#/components/schemas/ResourceAPIFeature' + SetFeatures_Response: + type: object + properties: {} + SpanInfo: + type: object + properties: + path: + type: string + carrier: + type: object + additionalProperties: + type: string + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + Sync_Request: + type: object + properties: + txId: + type: string + Sync_Response: + type: object + properties: {} + Update_Request: + type: object + properties: + controllerType: + type: string + filters: + type: object + additionalProperties: + $ref: '#/components/schemas/NotificationFilter' + resourceSchemas: + type: array + items: + $ref: '#/components/schemas/ResourceSchema' + Update_Response: + type: object + properties: {} + WriteAliasesAndUrls_Request: + type: object + properties: + controllerType: + type: string + aliasesToUrls: + type: object + additionalProperties: + type: string + WriteAliasesAndUrls_Response: + type: object + properties: {} tags: - - name: Platform + - name: Platform diff --git a/lib/node/pl-client/proto/plapi/plapiproto/resource_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/resource_types.proto index ff02387102..81a2592427 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/resource_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/resource_types.proto @@ -47,6 +47,7 @@ message NotificationFilter { optional bool resource_deleted = 3; optional bool resource_ready = 5; + optional bool resource_recovered = 18; optional bool resource_duplicate = 6; optional bool resource_error = 16; @@ -68,7 +69,7 @@ message NotificationFilter { optional bool generic_otw_set = 15; optional bool dynamic_changed = 11; - // next free 18; + // next free: 19; } message Payload { diff --git a/lib/node/pl-client/proto/plapi/plapiproto/ws-test.proto b/lib/node/pl-client/proto/plapi/plapiproto/ws-test.proto index de9c97721a..3cc28b5c21 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/ws-test.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/ws-test.proto @@ -1,5 +1,5 @@ // -// This is dirty implementation of custom protocol for fast WebSocket support testing in +// This is a dirty implementation of a custom protocol for fast WebSocket support testing in // environments with ZScaler or other similar MITM corporate proxies. // @@ -11,8 +11,8 @@ option go_package = "github.com/milaboratory/pl/plapi/plapiproto;plapiproto"; message PingWS { message Request { int32 request_id = 1 [json_name = "requestId"]; - int32 replies_count = 2 [json_name = "repliesCount"]; // amount of Response messages to send (0 - send nothing) - bool close = 3 [json_name = "close"]; // make server to close connection after sending responses + int32 replies_count = 2 [json_name = "repliesCount"]; // number of Response messages to send (0 - send nothing) + bool close = 3 [json_name = "close"]; // make the server close connection after sending responses } message Response { diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts index 13d72e9ca7..7518108c57 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.client.ts @@ -10,6 +10,15 @@ import type { MaintenanceAPI_Ping_Response } from "./api"; import type { MaintenanceAPI_Ping_Request } from "./api"; import type { MiscAPI_ListResourceTypes_Response } from "./api"; import type { MiscAPI_ListResourceTypes_Request } from "./api"; +import type { AuthAPI_ListUserResources_Response } from "./api"; +import type { AuthAPI_ListUserResources_Request } from "./api"; +import type { AuthAPI_ListGrants_Response } from "./api"; +import type { AuthAPI_ListGrants_Request } from "./api"; +import type { ServerStreamingCall } from "@protobuf-ts/runtime-rpc"; +import type { AuthAPI_RevokeGrant_Response } from "./api"; +import type { AuthAPI_RevokeGrant_Request } from "./api"; +import type { AuthAPI_GrantAccess_Response } from "./api"; +import type { AuthAPI_GrantAccess_Request } from "./api"; import type { AuthAPI_GetJWTToken_Response } from "./api"; import type { AuthAPI_GetJWTToken_Request } from "./api"; import type { AuthAPI_ListMethods_Response } from "./api"; @@ -155,28 +164,28 @@ export interface IPlatformClient { // /** - * LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: - * - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + * LockFieldValues gets the resource and obtains a lock on all resolved values of listed fields: + * - get the resource that will take the lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) * - list resource's fields, take fields with names set in request * - get resolved values of listed fields (IDs of 'ON' resources). * - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. * * Lock logic constraints: * - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them - * succeeds, while other fails with error (no long waiting) - * - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know - * resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. - * - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before - * being lockable. Attempt to lock resource that is not became original will fail. - * - Locking is one-way operation: it cannot be 'released' or 'revoked'. + * succeeds, while the other fails with an error (no long waiting) + * - Only resolved reference can be locked: to obtain a lock for a particular field's value, the backend needs to know + * the resource ID this field points to. Unless all listed field references are resolved to a final ID, the lock will fail. + * - Only an original resource can be locked: if a resource is 'pure' (supports deduplication), it has to pass deduplication before + * being lockable. An attempt to lock a resource that has not become original will fail. + * - Locking is a one-way operation: it cannot be 'released' or 'revoked'. * * @generated from protobuf rpc: LockFieldValues */ lockFieldValues(input: LocksAPI_LockFieldValues_Create_Request, options?: RpcOptions): UnaryCall; /** - * LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. - * Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. - * To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. + * LeaseResource creates a lease for a resource. A lease is a temporary lock that needs periodic renewal to stay valid. + * Leases are a separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + * To keep the lease active, the client needs the lease ID that is generated when a lease is created and used for lease updates. * * @generated from protobuf rpc: LeaseResource */ @@ -201,6 +210,22 @@ export interface IPlatformClient { * @generated from protobuf rpc: GetJWTToken */ getJWTToken(input: AuthAPI_GetJWTToken_Request, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: GrantAccess + */ + grantAccess(input: AuthAPI_GrantAccess_Request, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: RevokeGrant + */ + revokeGrant(input: AuthAPI_RevokeGrant_Request, options?: RpcOptions): UnaryCall; + /** + * @generated from protobuf rpc: ListGrants + */ + listGrants(input: AuthAPI_ListGrants_Request, options?: RpcOptions): ServerStreamingCall; + /** + * @generated from protobuf rpc: ListUserResources + */ + listUserResources(input: AuthAPI_ListUserResources_Request, options?: RpcOptions): ServerStreamingCall; /** * * Other stuff @@ -374,20 +399,20 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { // /** - * LockFieldValues gets resource and obtains a lock on all resolved values of listed fields: - * - get resource that will take lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) + * LockFieldValues gets the resource and obtains a lock on all resolved values of listed fields: + * - get the resource that will take the lock ('FOR' resource) (lock cannot be obtained 'FOR' or 'ON' deleted resource) * - list resource's fields, take fields with names set in request * - get resolved values of listed fields (IDs of 'ON' resources). * - acquire lock on all 'ON' resources, marking 'FOR' resource as an owner. * * Lock logic constraints: * - Locking is optimistic: if two processes try to obtain a lock on the same resource, one of them - * succeeds, while other fails with error (no long waiting) - * - Only resolved reference can be locked: to obtain a lock for particular field's value, backend needs to know - * resource ID this field points to. Unless all listed field references are resolved to final ID, lock will fail. - * - Only original resource can be locked: if resource is 'pure' (supports deduplication), it has to pass it before - * being lockable. Attempt to lock resource that is not became original will fail. - * - Locking is one-way operation: it cannot be 'released' or 'revoked'. + * succeeds, while the other fails with an error (no long waiting) + * - Only resolved reference can be locked: to obtain a lock for a particular field's value, the backend needs to know + * the resource ID this field points to. Unless all listed field references are resolved to a final ID, the lock will fail. + * - Only an original resource can be locked: if a resource is 'pure' (supports deduplication), it has to pass deduplication before + * being lockable. An attempt to lock a resource that has not become original will fail. + * - Locking is a one-way operation: it cannot be 'released' or 'revoked'. * * @generated from protobuf rpc: LockFieldValues */ @@ -396,9 +421,9 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { return stackIntercept("unary", this._transport, method, opt, input); } /** - * LeaseResource creates a lease for a resource. Lease is temporary lock that needs periodic renewal to stay actual. - * Leases are separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. - * To keep the lease active, client needs lease ID that is generated when lease is created and used for lease updates. + * LeaseResource creates a lease for a resource. A lease is a temporary lock that needs periodic renewal to stay valid. + * Leases are a separate mechanism from locks: leases are focused on 'clients', while locks are focused on 'resources'. + * To keep the lease active, the client needs the lease ID that is generated when a lease is created and used for lease updates. * * @generated from protobuf rpc: LeaseResource */ @@ -438,6 +463,34 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { const method = this.methods[23], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } + /** + * @generated from protobuf rpc: GrantAccess + */ + grantAccess(input: AuthAPI_GrantAccess_Request, options?: RpcOptions): UnaryCall { + const method = this.methods[24], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } + /** + * @generated from protobuf rpc: RevokeGrant + */ + revokeGrant(input: AuthAPI_RevokeGrant_Request, options?: RpcOptions): UnaryCall { + const method = this.methods[25], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } + /** + * @generated from protobuf rpc: ListGrants + */ + listGrants(input: AuthAPI_ListGrants_Request, options?: RpcOptions): ServerStreamingCall { + const method = this.methods[26], opt = this._transport.mergeOptions(options); + return stackIntercept("serverStreaming", this._transport, method, opt, input); + } + /** + * @generated from protobuf rpc: ListUserResources + */ + listUserResources(input: AuthAPI_ListUserResources_Request, options?: RpcOptions): ServerStreamingCall { + const method = this.methods[27], opt = this._transport.mergeOptions(options); + return stackIntercept("serverStreaming", this._transport, method, opt, input); + } /** * * Other stuff @@ -446,7 +499,7 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { * @generated from protobuf rpc: ListResourceTypes */ listResourceTypes(input: MiscAPI_ListResourceTypes_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[24], opt = this._transport.mergeOptions(options); + const method = this.methods[28], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** @@ -457,14 +510,14 @@ export class PlatformClient implements IPlatformClient, ServiceInfo { * @generated from protobuf rpc: Ping */ ping(input: MaintenanceAPI_Ping_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[25], opt = this._transport.mergeOptions(options); + const method = this.methods[29], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** * @generated from protobuf rpc: License */ license(input: MaintenanceAPI_License_Request, options?: RpcOptions): UnaryCall { - const method = this.methods[26], opt = this._transport.mergeOptions(options); + const method = this.methods[30], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } } diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts index 3ced0e08e4..7cf40b9317 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts @@ -27,22 +27,22 @@ import { ResourceType } from "./base_types"; import { Tx } from "./api_types"; import { Status } from "../../../../googleapis/googleapis/google/rpc/status"; /** - * Platform transactions on API level are implemented as bidirectional + * Platform transactions at the API level are implemented as bidirectional * streams that exist in parallel with transactions. * One stream = one transaction at all times. * - * Long story short from client side: + * Long story short from the client side: * - client initializes stream and immediately gets transaction info * message from server; * - client calls server methods by sending messages to the server, * each message has its own request ID starting from 1, request ID number * grows as new messages are sent to the server; - * - client reads server responses for each messages sent. Client can batch + * - client reads server responses for each message sent. Client can batch * writes and reads: e.g. send 3 messages, read 2 responses, send one * more, read 2; - * - to finish communication client sends 'commit' or 'discard' message and reads last + * - to finish communication, the client sends a 'commit' or 'discard' message and reads last * response; - * - to make server interrupt communication, client sends 'stream_close' message and reads to the last + * - to make the server interrupt communication, the client sends a 'stream_close' message and reads up to the last * server response. This allows graceful stream close, waiting for all queued server responses * to be sent before closing the stream. * @@ -52,24 +52,24 @@ import { Status } from "../../../../googleapis/googleapis/google/rpc/status"; * * 1. Initialization: * - * - client opens new bidirectional stream by calling 'Tx' gRPC method; - * - client sends 'tx_open' message with request_id = 0 to open RO or RW + * - the client opens a new bidirectional stream by calling 'Tx' gRPC method; + * - the client sends a 'tx_open' message with request_id = 0 to open RO or RW * transaction. - * Any message from client other than 'tx_open' during initialization is - * treated as communication error and causes server to close the stream; - * - server opens new transaction and sends transaction info to the client - * 'tx_open' server message; + * Any message from the client other than 'tx_open' during initialization is + * treated as a communication error and causes the server to close the stream; + * - the server opens a new transaction and sends transaction info to the client + * in a 'tx_open' server message; * * 2. Communication: * * Once the transaction gets initialized, all the communication between - * client and server should be considered as request-response sequence. - * The only difference to simple gRPC call is that sending a request does not - * block client until the response arrives from server. + * client and server should be considered a request-response sequence. + * The only difference from a simple gRPC call is that sending a request does not + * block the client until the response arrives from the server. * - * Client can send as many requests to the server as it wants before - * reading responses. This allows client to batch method calls, when it is not - * interested in result of each action, but needs only overall summary of + * The client can send as many requests to the server as it wants before + * reading responses. This allows the client to batch method calls, when it is not + * interested in the result of each action, but needs only the overall summary of * transaction commit. * For example, the following sequence is totally valid from client side: * 1. send reqA. @@ -79,69 +79,69 @@ import { Status } from "../../../../googleapis/googleapis/google/rpc/status"; * 5. get respB. * 6. get respC. * - * Here are detailed rules of client-server communication inside transaction + * Here are the detailed rules of client-server communication inside transaction * stream: - * - if stream is closed by client or interrupted by any unrecoverable - * communication error, the accompanied transaction is discarded; - * - each message in client stream (from client to server) is considered as - * method call. - * E.g. 'resource_remove' makes server to actually remove resource inside - * transaction, bound to current stream; - * - each message in client stream MUST have its own ID (request_id), - * generated by client, UNIQUE for transaction. Numeration starts from - * 1 for each at this step. request_id == 0 is treated as unrecoverable - * communication error by server and cancels transaction; - * - server expects 'request_id' from client to grow constantly by one as + * - if the stream is closed by the client or interrupted by any unrecoverable + * communication error, the accompanying transaction is discarded; + * - each message in the client stream (from client to server) is considered + * a method call. + * E.g. 'resource_remove' makes the server actually remove the resource inside + * the transaction bound to the current stream; + * - each message in the client stream MUST have its own ID (request_id), + * generated by the client, UNIQUE for the transaction. Numbering starts from + * 1 for each new transaction. request_id == 0 is treated as an unrecoverable + * communication error by the server and cancels the transaction; + * - server expects 'request_id' from the client to grow consecutively by one as * messages come. If message M2 arrives next to M1 and * M2.request_id != (M1.request_id + 1) - * server treats it as unrecoverable communication error and cancels - * transaction; - * - all messages in server stream (from server to client) in communication - * stage have the same 'request_id' as the client messages, that triggered - * the operation. This allows client to match server responses to the + * server treats it as an unrecoverable communication error and cancels + * the transaction; + * - all messages in the server stream (from server to client) in the communication + * stage have the same 'request_id' as the client messages that triggered + * the operation. This allows the client to match server responses to the * requests it sent earlier; - * - messages order in server stream matches order in client stream: if - * client sent sequence [ReqA, ReqB], the server will always response + * - message order in the server stream matches the order in the client stream: if + * the client sent sequence [ReqA, ReqB], the server will always respond with * [RespA, RespB]; - * - server can send several responses to single client request (multi-message + * - the server can send several responses to a single client request (multi-message * response). In that case, all such responses will have the same 'request_id' - * (the one from client request), but different message IDs. See + * (the one from the client request), but different message IDs. See * 'Multi-message responses' section below for more info. - * - client is allowed (but not obligated) to generate local IDs for new + * - the client is allowed (but not obligated) to generate local IDs for new * instances and use them in links and other references. These local IDs - * are valid only within current transaction; - * - any instance in server stream always has _real_ global ID that is valid - * at any times in any other request outside current transaction; + * are valid only within the current transaction; + * - any instance in the server stream always has a _real_ global ID that is valid + * at any time in any other request outside the current transaction; * * 3. Finalization: * - * - client finalizes transaction by sending 'tx_commit' or 'tx_discard' - * message to the server. After that client can close client stream as any + * - the client finalizes the transaction by sending a 'tx_commit' or 'tx_discard' + * message to the server. After that, the client can close the client stream as any * message sent to the server after commit/discard will be ignored anyway; - * - server stops reading client stream and does the commit/discard action. - * - once transaction is closed, server sends the result to client and - * closes server stream. - * - client stream reading may be interrupted by special 'stream_close' message, which - * causes transaction automatic discard and does not produce additional reply message. + * - the server stops reading the client stream and does the commit/discard action. + * - once the transaction is closed, the server sends the result to the client and + * closes the server stream. + * - the client stream reading may be interrupted by a special 'stream_close' message, which + * causes automatic transaction discard and does not produce an additional reply message. * This 'stream_close' message also does not require any 'request_id' value and does not - * produce any response from server side. + * produce any response from the server side. * - * At this point the transaction over gRPC is considered as finalized, all + * At this point the transaction over gRPC is considered finalized, all * local IDs generated within the transaction are no longer valid. * * Multi-message responses * * Some transaction methods produce several messages by design, causing - * single call to result in multiple responses. Listings are the clear + * a single call to result in multiple responses. Listings are a clear * example of that. - * All responses from server have special field (multi_message) with all meta - * info on multi-message response: + * All responses from the server have a special field (multi_message) with all meta + * info on multi-message responses: * - for single-message responses, multi_message is always empty; * - for multi-message responses, multi_message.id is always > 0; - * - all messages in multi-message response have request_id equal to request_id from - * original client's request message; - * - last message in multi-message response always has multi_message.is_last = true; - * - empty multi-message response always has: + * - all messages in a multi-message response have request_id equal to request_id from + * the original client's request message; + * - the last message in a multi-message response always has multi_message.is_last = true; + * - an empty multi-message response always has: * multi_message.is_last = true; * multi_message.is_empty = true. * @@ -182,15 +182,15 @@ export interface TxAPI_ClientMessage { } | { oneofKind: "streamClose"; /** - * Interrupt the transaction network stream on server side. + * Interrupt the transaction network stream on the server side. * - * This allows to gracefully close network connection and discard the associated transaction, + * This allows one to gracefully close the network connection and discard the associated transaction, * ensuring all pending server responses are sent to the client before closing. - * We use this in RO transactions to imitate the feature of 'half-open' connection + * We use this in RO transactions to imitate the feature of a 'half-open' connection * available in pure gRPC, when we work with WebSockets. - * Pure gRPC uses 'end of client messages stream' as a criteria for graceful discard. - * Pure WebSocket does not allow this, breaking entire connection at once. stream_close helps to get - * behaviour as we have for gRPC. + * Pure gRPC uses 'end of the client message stream' as a criterion for graceful discard. + * Pure WebSocket does not allow this, breaking the entire connection at once. stream_close helps + * achieve the same behavior as in gRPC. * * @generated from protobuf field: MiLaboratories.PL.API.TxAPI.CloseStream.Request stream_close = 14 */ @@ -278,7 +278,7 @@ export interface TxAPI_ClientMessage { /** * @generated from protobuf field: MiLaboratories.PL.API.ResourceAPI.SetError.Request resource_set_error = 61 */ - resourceSetError: ResourceAPI_SetError_Request; // create a special field and set error there. + resourceSetError: ResourceAPI_SetError_Request; // create a special field and set an error there. } | { oneofKind: "resourceListByType"; /** @@ -332,7 +332,7 @@ export interface TxAPI_ClientMessage { /** * @generated from protobuf field: MiLaboratories.PL.API.FieldAPI.Exists.Request field_exists = 107 */ - fieldExists: FieldAPI_Exists_Request; // add field to resource + fieldExists: FieldAPI_Exists_Request; // check if field exists } | { oneofKind: "fieldSet"; /** @@ -386,7 +386,7 @@ export interface TxAPI_ClientMessage { /** * @generated from protobuf field: MiLaboratories.PL.API.SubscriptionAPI.DetachFilter.Request subscription_detach_filter = 113 */ - subscriptionDetachFilter: SubscriptionAPI_DetachFilter_Request; // add filter to existing subscription + subscriptionDetachFilter: SubscriptionAPI_DetachFilter_Request; // detach filter from existing subscription } | { oneofKind: "subscriptionCreateFilter"; /** @@ -404,13 +404,13 @@ export interface TxAPI_ClientMessage { /** * @generated from protobuf field: MiLaboratories.PL.API.NotificationAPI.Ack.Request notification_ack = 155 */ - notificationAck: NotificationAPI_Ack_Request; // acknowledge notification handle by controller + notificationAck: NotificationAPI_Ack_Request; // acknowledge notification handled by controller } | { oneofKind: "notificationDiscard"; /** * @generated from protobuf field: MiLaboratories.PL.API.NotificationAPI.Discard.Request notification_discard = 156 */ - notificationDiscard: NotificationAPI_Discard_Request; // discard notification handle by controller + notificationDiscard: NotificationAPI_Discard_Request; // discard notification handled by controller } | { oneofKind: "resourceKeyValueSet"; /** @@ -919,7 +919,7 @@ export interface TxAPI_ServerMessage_Multi { * Sequential message ID for multi-message response, starting from 1. * Caller can use 'id > 0' check as a sign of multi-message response. * Some API requests produce several messages in response by design (say, listings) - * In that case, the server responses to the client with many messages, each having + * In that case, the server responds to the client with many messages, each having * the same value and different values. * * @generated from protobuf field: uint32 id = 1 @@ -934,7 +934,7 @@ export interface TxAPI_ServerMessage_Multi { isLast: boolean; /** * Sign of empty multi-message response. Some multi-message responses can produce nothing - * (like listing of empty directory). In that case client still has to know that the request was + * (like a listing of an empty directory). In that case, the client still has to know that the request was * handled and the empty result is OK. * * @generated from protobuf field: bool is_empty = 3 @@ -1492,9 +1492,9 @@ export interface ResourceAPI_List_ByType_Request { */ resourceType?: ResourceType; /** - * Non-zero value makes API to limit its responses count to at most + * Non-zero value makes the API limit its response count to at most * number of items. - * Zero value makes API to return all available items. + * Zero value makes the API return all available items. * * @generated from protobuf field: uint32 limit = 3 */ @@ -1507,7 +1507,7 @@ export interface ResourceAPI_List_ByType_Request { */ after: bigint; /** - * True value makes API to return original resources instead of duplicates. + * True value makes the API return original resources instead of duplicates. * * @generated from protobuf field: bool resolve_duplicates = 4 */ @@ -1564,7 +1564,7 @@ export interface ResourceAPI_CreateRoot_Response { // FIXME: add CreateResource method to API /** - * Remove any resource, that has garbage collection disabled + * Remove any resource that has garbage collection disabled * * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Remove */ @@ -1606,6 +1606,10 @@ export interface ResourceAPI_Name_Set_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: string name = 2 */ @@ -1703,11 +1707,11 @@ export interface ResourceAPI_Tree_Request { */ resourceSignature: Uint8Array; /** - * Limit maximum depth the tree is traversed. - * The resource is considered on depth = 0, the values of its fiels + * Limit the maximum depth the tree is traversed. + * The resource is considered at depth = 0, the values of its fields * are on depth = 1 and so on. - * The maximum uint32 value disables the limit at all. - * 0 value makes API to return only single resource and is actually + * The maximum uint32 value disables the limit entirely. + * A 0 value makes the API return only a single resource and is actually * equal to Get.Request * * @generated from protobuf field: optional uint32 max_depth = 2 @@ -1749,7 +1753,7 @@ export interface ResourceAPI_TreeSize_Request { export interface ResourceAPI_TreeSize_Response { /** * size of all tree resources in bytes - * could change between call regarding compression algorithm + * could change between calls depending on compression algorithm * * @generated from protobuf field: uint64 size = 1 */ @@ -1952,9 +1956,9 @@ export interface FieldAPI_List_Request { */ startFrom: string; /** - * Non-zero value makes API to limit number of returned fields to at + * Non-zero value makes the API limit the number of returned fields to at * most . - * Zero value makes API to return all available fields of the resource. + * Zero value makes the API return all available fields of the resource. * * @generated from protobuf field: uint32 limit = 3 */ @@ -1974,7 +1978,7 @@ export interface FieldAPI_List_Response { * The name of field next to the current one. * * Is not empty only for the last message in the current listing, when was > 0 in List.Request AND - * there is more items to read (the listing was stopped because of that limit) + * there are more items to read (the listing was stopped because of that limit) * * Use value as in the List.Request to continue listing. * @@ -2567,9 +2571,9 @@ export interface ResourceKVAPI_List_Request { */ startFrom: string; /** - * Non-zero value makes API to limit its responses count to at most + * Non-zero value makes the API limit its response count to at most * number of items. - * Zero value makes API to return all available items. + * Zero value makes the API return all available items. * * @generated from protobuf field: uint32 limit = 3 */ @@ -2589,7 +2593,7 @@ export interface ResourceKVAPI_List_Response { * The key of the KV item next to the last returned item. * * Is not empty only for the last message in the current listing, when was > 0 in List.Request AND - * there is more items to read (the listing was stopped because of that limit) + * there are more items to read (the listing was stopped because of that limit) * * Use value as of the List.Request to continue listing. * @@ -3025,7 +3029,7 @@ export interface LocksAPI_LockFieldValues_Create_Request { /** * @generated from protobuf field: repeated string lock_references_of = 2 */ - lockReferencesOf: string[]; // list of fields, which values should be locked + lockReferencesOf: string[]; // list of fields whose values should be locked /** * @generated from protobuf field: string comment = 3 */ @@ -3037,7 +3041,7 @@ export interface LocksAPI_LockFieldValues_Create_Request { export interface LocksAPI_LockFieldValues_Create_Response { /** * true when lock was acquired (new, or already owned by the owner) - * Client MUST pay attention to this flag, as it shows if lock was successfull. + * Client MUST pay attention to this flag, as it shows if lock was successful. * * @generated from protobuf field: bool acquired = 1 */ @@ -3279,6 +3283,226 @@ export enum AuthAPI_GetJWTToken_Role { */ WORKFLOW = 3 } +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.GrantAccess + */ +export interface AuthAPI_GrantAccess { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.GrantAccess.Request + */ +export interface AuthAPI_GrantAccess_Request { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; + /** + * @generated from protobuf field: string target_user = 3 + */ + targetUser: string; // user login to grant access to + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions = 4 + */ + permissions?: AuthAPI_Grant_Permissions; // access level to grant +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.GrantAccess.Response + */ +export interface AuthAPI_GrantAccess_Response { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.RevokeGrant + */ +export interface AuthAPI_RevokeGrant { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.RevokeGrant.Request + */ +export interface AuthAPI_RevokeGrant_Request { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; + /** + * @generated from protobuf field: string target_user = 3 + */ + targetUser: string; // user login whose grant to revoke +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.RevokeGrant.Response + */ +export interface AuthAPI_RevokeGrant_Response { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants + */ +export interface AuthAPI_ListGrants { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants.Request + */ +export interface AuthAPI_ListGrants_Request { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants.Response + */ +export interface AuthAPI_ListGrants_Response { + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.Grant grant = 1 + */ + grant?: AuthAPI_Grant; // one per stream message +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.Grant + */ +export interface AuthAPI_Grant { + /** + * @generated from protobuf field: string user = 1 + */ + user: string; + /** + * @generated from protobuf field: uint64 resource_id = 2 + */ + resourceId: bigint; + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions = 3 + */ + permissions?: AuthAPI_Grant_Permissions; // access level from grant table + /** + * @generated from protobuf field: string granted_by = 4 + */ + grantedBy: string; // login of the user who created this grant + /** + * @generated from protobuf field: int64 granted_at = 5 + */ + grantedAt: bigint; // unix timestamp in milliseconds +} +/** + * Permissions describes access level for a grant. + * + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.Grant.Permissions + */ +export interface AuthAPI_Grant_Permissions { + /** + * @generated from protobuf field: bool writable = 1 + */ + writable: boolean; // write access = ability to modify resource tree + share access +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources + */ +export interface AuthAPI_ListUserResources { +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.Request + */ +export interface AuthAPI_ListUserResources_Request { + /** + * User login to list resources for. + * Is ignored for regular user role: it can list only its own resources. + * + * @generated from protobuf field: string login = 1 + */ + login: string; + /** + * Start listing shared resources from given resource ID. + * User root is returned only on the first page (when start_from == 0). + * + * @generated from protobuf field: uint64 start_from = 2 + */ + startFrom: bigint; + /** + * Non-zero value limits total resource count to at most . + * limit = 1 with start_from = 0 returns solely user's root + * limit = 2 with non-zero start_from returns 2 shared resources. + * Zero returns all resources. + * + * @generated from protobuf field: uint32 limit = 3 + */ + limit: number; +} +/** + * Multi-message: first message contains UserRoot (only when start_from == 0), + * + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.Response + */ +export interface AuthAPI_ListUserResources_Response { + /** + * @generated from protobuf oneof: entry + */ + entry: { + oneofKind: "userRoot"; + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot user_root = 1 + */ + userRoot: AuthAPI_ListUserResources_UserRoot; + } | { + oneofKind: "sharedResource"; + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource shared_resource = 2 + */ + sharedResource: AuthAPI_ListUserResources_SharedResource; + } | { + oneofKind: undefined; + }; + /** + * Set only on the last message when limit > 0 and more items exist. + * Use as 'start_from' in the next request to continue listing. + * + * @generated from protobuf field: uint64 next_resource_id = 3 + */ + nextResourceId: bigint; +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot + */ +export interface AuthAPI_ListUserResources_UserRoot { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; +} +/** + * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource + */ +export interface AuthAPI_ListUserResources_SharedResource { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; + /** + * @generated from protobuf field: MiLaboratories.PL.Base.ResourceType resource_type = 3 + */ + resourceType?: ResourceType; + /** + * @generated from protobuf field: MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions = 4 + */ + permissions?: AuthAPI_Grant_Permissions; +} /** * @generated from protobuf message MiLaboratories.PL.API.MiscAPI */ @@ -3332,10 +3556,6 @@ export interface MaintenanceAPI_Ping_Response { * @generated from protobuf field: string core_full_version = 2 */ coreFullVersion: string; - /** - * @generated from protobuf field: string server_info = 3 - */ - serverInfo: string; /** * @generated from protobuf field: MiLaboratories.PL.API.MaintenanceAPI.Ping.Response.Compression compression = 4 */ @@ -7947,12 +8167,14 @@ class ResourceAPI_Name_Set_Request$Type extends MessageType): ResourceAPI_Name_Set_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.name = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -7966,6 +8188,9 @@ class ResourceAPI_Name_Set_Request$Type extends MessageType { +class AuthAPI_GrantAccess$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MiscAPI", []); + super("MiLaboratories.PL.API.AuthAPI.GrantAccess", []); } - create(value?: PartialMessage): MiscAPI { + create(value?: PartialMessage): AuthAPI_GrantAccess { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI): MiscAPI { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_GrantAccess): AuthAPI_GrantAccess { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -16298,7 +16526,7 @@ class MiscAPI$Type extends MessageType { } return message; } - internalBinaryWrite(message: MiscAPI, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + internalBinaryWrite(message: AuthAPI_GrantAccess, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -16306,25 +16534,45 @@ class MiscAPI$Type extends MessageType { } } /** - * @generated MessageType for protobuf message MiLaboratories.PL.API.MiscAPI + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.GrantAccess */ -export const MiscAPI = new MiscAPI$Type(); +export const AuthAPI_GrantAccess = new AuthAPI_GrantAccess$Type(); // @generated message type with reflection information, may provide speed optimized methods -class MiscAPI_ListResourceTypes$Type extends MessageType { +class AuthAPI_GrantAccess_Request$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes", []); + super("MiLaboratories.PL.API.AuthAPI.GrantAccess.Request", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "target_user", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 4, name: "permissions", kind: "message", T: () => AuthAPI_Grant_Permissions } + ]); } - create(value?: PartialMessage): MiscAPI_ListResourceTypes { + create(value?: PartialMessage): AuthAPI_GrantAccess_Request { const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + message.targetUser = ""; if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes): MiscAPI_ListResourceTypes { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_GrantAccess_Request): AuthAPI_GrantAccess_Request { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + case /* string target_user */ 3: + message.targetUser = reader.string(); + break; + case /* MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions */ 4: + message.permissions = AuthAPI_Grant_Permissions.internalBinaryRead(reader, reader.uint32(), options, message.permissions); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -16336,7 +16584,19 @@ class MiscAPI_ListResourceTypes$Type extends MessageType { +class AuthAPI_GrantAccess_Response$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Request", []); + super("MiLaboratories.PL.API.AuthAPI.GrantAccess.Response", []); } - create(value?: PartialMessage): MiscAPI_ListResourceTypes_Request { + create(value?: PartialMessage): AuthAPI_GrantAccess_Response { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes_Request): MiscAPI_ListResourceTypes_Request { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_GrantAccess_Response): AuthAPI_GrantAccess_Response { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -16374,7 +16634,7 @@ class MiscAPI_ListResourceTypes_Request$Type extends MessageType { +class AuthAPI_RevokeGrant$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Response", [ - { no: 1, name: "types", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => ResourceType } - ]); + super("MiLaboratories.PL.API.AuthAPI.RevokeGrant", []); } - create(value?: PartialMessage): MiscAPI_ListResourceTypes_Response { + create(value?: PartialMessage): AuthAPI_RevokeGrant { const message = globalThis.Object.create((this.messagePrototype!)); - message.types = []; if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes_Response): MiscAPI_ListResourceTypes_Response { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_RevokeGrant): AuthAPI_RevokeGrant { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { - case /* repeated MiLaboratories.PL.Base.ResourceType types */ 1: - message.types.push(ResourceType.internalBinaryRead(reader, reader.uint32(), options)); - break; default: let u = options.readUnknownField; if (u === "throw") @@ -16418,10 +16672,7 @@ class MiscAPI_ListResourceTypes_Response$Type extends MessageType { +class AuthAPI_RevokeGrant_Request$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MaintenanceAPI", []); + super("MiLaboratories.PL.API.AuthAPI.RevokeGrant.Request", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "target_user", kind: "scalar", T: 9 /*ScalarType.STRING*/ } + ]); } - create(value?: PartialMessage): MaintenanceAPI { + create(value?: PartialMessage): AuthAPI_RevokeGrant_Request { const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + message.targetUser = ""; if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI): MaintenanceAPI { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_RevokeGrant_Request): AuthAPI_RevokeGrant_Request { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + case /* string target_user */ 3: + message.targetUser = reader.string(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -16459,7 +16726,16 @@ class MaintenanceAPI$Type extends MessageType { } return message; } - internalBinaryWrite(message: MaintenanceAPI, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + internalBinaryWrite(message: AuthAPI_RevokeGrant_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); + /* string target_user = 3; */ + if (message.targetUser !== "") + writer.tag(3, WireType.LengthDelimited).string(message.targetUser); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -16467,21 +16743,21 @@ class MaintenanceAPI$Type extends MessageType { } } /** - * @generated MessageType for protobuf message MiLaboratories.PL.API.MaintenanceAPI + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.RevokeGrant.Request */ -export const MaintenanceAPI = new MaintenanceAPI$Type(); +export const AuthAPI_RevokeGrant_Request = new AuthAPI_RevokeGrant_Request$Type(); // @generated message type with reflection information, may provide speed optimized methods -class MaintenanceAPI_Ping$Type extends MessageType { +class AuthAPI_RevokeGrant_Response$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MaintenanceAPI.Ping", []); + super("MiLaboratories.PL.API.AuthAPI.RevokeGrant.Response", []); } - create(value?: PartialMessage): MaintenanceAPI_Ping { + create(value?: PartialMessage): AuthAPI_RevokeGrant_Response { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI_Ping): MaintenanceAPI_Ping { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_RevokeGrant_Response): AuthAPI_RevokeGrant_Response { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -16497,7 +16773,7 @@ class MaintenanceAPI_Ping$Type extends MessageType { } return message; } - internalBinaryWrite(message: MaintenanceAPI_Ping, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + internalBinaryWrite(message: AuthAPI_RevokeGrant_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -16505,21 +16781,21 @@ class MaintenanceAPI_Ping$Type extends MessageType { } } /** - * @generated MessageType for protobuf message MiLaboratories.PL.API.MaintenanceAPI.Ping + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.RevokeGrant.Response */ -export const MaintenanceAPI_Ping = new MaintenanceAPI_Ping$Type(); +export const AuthAPI_RevokeGrant_Response = new AuthAPI_RevokeGrant_Response$Type(); // @generated message type with reflection information, may provide speed optimized methods -class MaintenanceAPI_Ping_Request$Type extends MessageType { +class AuthAPI_ListGrants$Type extends MessageType { constructor() { - super("MiLaboratories.PL.API.MaintenanceAPI.Ping.Request", []); + super("MiLaboratories.PL.API.AuthAPI.ListGrants", []); } - create(value?: PartialMessage): MaintenanceAPI_Ping_Request { + create(value?: PartialMessage): AuthAPI_ListGrants { const message = globalThis.Object.create((this.messagePrototype!)); if (value !== undefined) - reflectionMergePartial(this, message, value); + reflectionMergePartial(this, message, value); return message; } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI_Ping_Request): MaintenanceAPI_Ping_Request { + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListGrants): AuthAPI_ListGrants { let message = target ?? this.create(), end = reader.pos + length; while (reader.pos < end) { let [fieldNo, wireType] = reader.tag(); @@ -16535,7 +16811,7 @@ class MaintenanceAPI_Ping_Request$Type extends MessageType { +class AuthAPI_ListGrants_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListGrants.Request", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_ListGrants_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListGrants_Request): AuthAPI_ListGrants_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListGrants_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants.Request + */ +export const AuthAPI_ListGrants_Request = new AuthAPI_ListGrants_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListGrants_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListGrants.Response", [ + { no: 1, name: "grant", kind: "message", T: () => AuthAPI_Grant } + ]); + } + create(value?: PartialMessage): AuthAPI_ListGrants_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListGrants_Response): AuthAPI_ListGrants_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* MiLaboratories.PL.API.AuthAPI.Grant grant */ 1: + message.grant = AuthAPI_Grant.internalBinaryRead(reader, reader.uint32(), options, message.grant); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListGrants_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* MiLaboratories.PL.API.AuthAPI.Grant grant = 1; */ + if (message.grant) + AuthAPI_Grant.internalBinaryWrite(message.grant, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants.Response + */ +export const AuthAPI_ListGrants_Response = new AuthAPI_ListGrants_Response$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_Grant$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.Grant", [ + { no: 1, name: "user", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "permissions", kind: "message", T: () => AuthAPI_Grant_Permissions }, + { no: 4, name: "granted_by", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 5, name: "granted_at", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_Grant { + const message = globalThis.Object.create((this.messagePrototype!)); + message.user = ""; + message.resourceId = 0n; + message.grantedBy = ""; + message.grantedAt = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_Grant): AuthAPI_Grant { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string user */ 1: + message.user = reader.string(); + break; + case /* uint64 resource_id */ 2: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions */ 3: + message.permissions = AuthAPI_Grant_Permissions.internalBinaryRead(reader, reader.uint32(), options, message.permissions); + break; + case /* string granted_by */ 4: + message.grantedBy = reader.string(); + break; + case /* int64 granted_at */ 5: + message.grantedAt = reader.int64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_Grant, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string user = 1; */ + if (message.user !== "") + writer.tag(1, WireType.LengthDelimited).string(message.user); + /* uint64 resource_id = 2; */ + if (message.resourceId !== 0n) + writer.tag(2, WireType.Varint).uint64(message.resourceId); + /* MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions = 3; */ + if (message.permissions) + AuthAPI_Grant_Permissions.internalBinaryWrite(message.permissions, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* string granted_by = 4; */ + if (message.grantedBy !== "") + writer.tag(4, WireType.LengthDelimited).string(message.grantedBy); + /* int64 granted_at = 5; */ + if (message.grantedAt !== 0n) + writer.tag(5, WireType.Varint).int64(message.grantedAt); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.Grant + */ +export const AuthAPI_Grant = new AuthAPI_Grant$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_Grant_Permissions$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.Grant.Permissions", [ + { no: 1, name: "writable", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_Grant_Permissions { + const message = globalThis.Object.create((this.messagePrototype!)); + message.writable = false; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_Grant_Permissions): AuthAPI_Grant_Permissions { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* bool writable */ 1: + message.writable = reader.bool(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_Grant_Permissions, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* bool writable = 1; */ + if (message.writable !== false) + writer.tag(1, WireType.Varint).bool(message.writable); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.Grant.Permissions + */ +export const AuthAPI_Grant_Permissions = new AuthAPI_Grant_Permissions$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListUserResources$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListUserResources", []); + } + create(value?: PartialMessage): AuthAPI_ListUserResources { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListUserResources): AuthAPI_ListUserResources { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListUserResources, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources + */ +export const AuthAPI_ListUserResources = new AuthAPI_ListUserResources$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListUserResources_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListUserResources.Request", [ + { no: 1, name: "login", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "start_from", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "limit", kind: "scalar", T: 13 /*ScalarType.UINT32*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_ListUserResources_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + message.login = ""; + message.startFrom = 0n; + message.limit = 0; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListUserResources_Request): AuthAPI_ListUserResources_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* string login */ 1: + message.login = reader.string(); + break; + case /* uint64 start_from */ 2: + message.startFrom = reader.uint64().toBigInt(); + break; + case /* uint32 limit */ 3: + message.limit = reader.uint32(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListUserResources_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* string login = 1; */ + if (message.login !== "") + writer.tag(1, WireType.LengthDelimited).string(message.login); + /* uint64 start_from = 2; */ + if (message.startFrom !== 0n) + writer.tag(2, WireType.Varint).uint64(message.startFrom); + /* uint32 limit = 3; */ + if (message.limit !== 0) + writer.tag(3, WireType.Varint).uint32(message.limit); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.Request + */ +export const AuthAPI_ListUserResources_Request = new AuthAPI_ListUserResources_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListUserResources_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListUserResources.Response", [ + { no: 1, name: "user_root", kind: "message", oneof: "entry", T: () => AuthAPI_ListUserResources_UserRoot }, + { no: 2, name: "shared_resource", kind: "message", oneof: "entry", T: () => AuthAPI_ListUserResources_SharedResource }, + { no: 3, name: "next_resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_ListUserResources_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + message.entry = { oneofKind: undefined }; + message.nextResourceId = 0n; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListUserResources_Response): AuthAPI_ListUserResources_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot user_root */ 1: + message.entry = { + oneofKind: "userRoot", + userRoot: AuthAPI_ListUserResources_UserRoot.internalBinaryRead(reader, reader.uint32(), options, (message.entry as any).userRoot) + }; + break; + case /* MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource shared_resource */ 2: + message.entry = { + oneofKind: "sharedResource", + sharedResource: AuthAPI_ListUserResources_SharedResource.internalBinaryRead(reader, reader.uint32(), options, (message.entry as any).sharedResource) + }; + break; + case /* uint64 next_resource_id */ 3: + message.nextResourceId = reader.uint64().toBigInt(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListUserResources_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot user_root = 1; */ + if (message.entry.oneofKind === "userRoot") + AuthAPI_ListUserResources_UserRoot.internalBinaryWrite(message.entry.userRoot, writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + /* MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource shared_resource = 2; */ + if (message.entry.oneofKind === "sharedResource") + AuthAPI_ListUserResources_SharedResource.internalBinaryWrite(message.entry.sharedResource, writer.tag(2, WireType.LengthDelimited).fork(), options).join(); + /* uint64 next_resource_id = 3; */ + if (message.nextResourceId !== 0n) + writer.tag(3, WireType.Varint).uint64(message.nextResourceId); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.Response + */ +export const AuthAPI_ListUserResources_Response = new AuthAPI_ListUserResources_Response$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListUserResources_UserRoot$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): AuthAPI_ListUserResources_UserRoot { + const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListUserResources_UserRoot): AuthAPI_ListUserResources_UserRoot { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListUserResources_UserRoot, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.UserRoot + */ +export const AuthAPI_ListUserResources_UserRoot = new AuthAPI_ListUserResources_UserRoot$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class AuthAPI_ListUserResources_SharedResource$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "resource_type", kind: "message", T: () => ResourceType }, + { no: 4, name: "permissions", kind: "message", T: () => AuthAPI_Grant_Permissions } + ]); + } + create(value?: PartialMessage): AuthAPI_ListUserResources_SharedResource { + const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: AuthAPI_ListUserResources_SharedResource): AuthAPI_ListUserResources_SharedResource { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + case /* MiLaboratories.PL.Base.ResourceType resource_type */ 3: + message.resourceType = ResourceType.internalBinaryRead(reader, reader.uint32(), options, message.resourceType); + break; + case /* MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions */ 4: + message.permissions = AuthAPI_Grant_Permissions.internalBinaryRead(reader, reader.uint32(), options, message.permissions); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: AuthAPI_ListUserResources_SharedResource, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); + /* MiLaboratories.PL.Base.ResourceType resource_type = 3; */ + if (message.resourceType) + ResourceType.internalBinaryWrite(message.resourceType, writer.tag(3, WireType.LengthDelimited).fork(), options).join(); + /* MiLaboratories.PL.API.AuthAPI.Grant.Permissions permissions = 4; */ + if (message.permissions) + AuthAPI_Grant_Permissions.internalBinaryWrite(message.permissions, writer.tag(4, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource + */ +export const AuthAPI_ListUserResources_SharedResource = new AuthAPI_ListUserResources_SharedResource$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MiscAPI$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MiscAPI", []); + } + create(value?: PartialMessage): MiscAPI { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI): MiscAPI { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MiscAPI, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MiscAPI + */ +export const MiscAPI = new MiscAPI$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MiscAPI_ListResourceTypes$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes", []); + } + create(value?: PartialMessage): MiscAPI_ListResourceTypes { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes): MiscAPI_ListResourceTypes { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MiscAPI_ListResourceTypes, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MiscAPI.ListResourceTypes + */ +export const MiscAPI_ListResourceTypes = new MiscAPI_ListResourceTypes$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MiscAPI_ListResourceTypes_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Request", []); + } + create(value?: PartialMessage): MiscAPI_ListResourceTypes_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes_Request): MiscAPI_ListResourceTypes_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MiscAPI_ListResourceTypes_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Request + */ +export const MiscAPI_ListResourceTypes_Request = new MiscAPI_ListResourceTypes_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MiscAPI_ListResourceTypes_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Response", [ + { no: 1, name: "types", kind: "message", repeat: 2 /*RepeatType.UNPACKED*/, T: () => ResourceType } + ]); + } + create(value?: PartialMessage): MiscAPI_ListResourceTypes_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + message.types = []; + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MiscAPI_ListResourceTypes_Response): MiscAPI_ListResourceTypes_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* repeated MiLaboratories.PL.Base.ResourceType types */ 1: + message.types.push(ResourceType.internalBinaryRead(reader, reader.uint32(), options)); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MiscAPI_ListResourceTypes_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* repeated MiLaboratories.PL.Base.ResourceType types = 1; */ + for (let i = 0; i < message.types.length; i++) + ResourceType.internalBinaryWrite(message.types[i], writer.tag(1, WireType.LengthDelimited).fork(), options).join(); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MiscAPI.ListResourceTypes.Response + */ +export const MiscAPI_ListResourceTypes_Response = new MiscAPI_ListResourceTypes_Response$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MaintenanceAPI$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MaintenanceAPI", []); + } + create(value?: PartialMessage): MaintenanceAPI { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI): MaintenanceAPI { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MaintenanceAPI, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MaintenanceAPI + */ +export const MaintenanceAPI = new MaintenanceAPI$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MaintenanceAPI_Ping$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MaintenanceAPI.Ping", []); + } + create(value?: PartialMessage): MaintenanceAPI_Ping { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI_Ping): MaintenanceAPI_Ping { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MaintenanceAPI_Ping, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MaintenanceAPI.Ping + */ +export const MaintenanceAPI_Ping = new MaintenanceAPI_Ping$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MaintenanceAPI_Ping_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.PL.API.MaintenanceAPI.Ping.Request", []); + } + create(value?: PartialMessage): MaintenanceAPI_Ping_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: MaintenanceAPI_Ping_Request): MaintenanceAPI_Ping_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: MaintenanceAPI_Ping_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.PL.API.MaintenanceAPI.Ping.Request + */ +export const MaintenanceAPI_Ping_Request = new MaintenanceAPI_Ping_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class MaintenanceAPI_Ping_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.MaintenanceAPI.Ping.Response", [ { no: 1, name: "core_version", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, { no: 2, name: "core_full_version", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, - { no: 3, name: "server_info", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, { no: 4, name: "compression", kind: "enum", T: () => ["MiLaboratories.PL.API.MaintenanceAPI.Ping.Response.Compression", MaintenanceAPI_Ping_Response_Compression] }, { no: 5, name: "instance_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, { no: 6, name: "platform", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, @@ -16564,7 +17633,6 @@ class MaintenanceAPI_Ping_Response$Type extends MessageType { constructor() { super("MiLaboratories.PL.Base.FieldRef", [ { no: 2, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 3, name: "field_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): FieldRef { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.fieldName = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -148,6 +154,9 @@ class FieldRef$Type extends MessageType { case /* uint64 resource_id */ 2: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 4: + message.resourceSignature = reader.bytes(); + break; case /* string field_name */ 3: message.fieldName = reader.string(); break; @@ -169,6 +178,9 @@ class FieldRef$Type extends MessageType { /* string field_name = 3; */ if (message.fieldName !== "") writer.tag(3, WireType.LengthDelimited).string(message.fieldName); + /* bytes resource_signature = 4; */ + if (message.resourceSignature.length) + writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/import.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/import.ts index e2f0e897e6..61278576eb 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/import.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/import.ts @@ -27,8 +27,8 @@ import { BytesValue } from "../../../../../google/protobuf/wrappers"; import { Any } from "../../../../../google/protobuf/any"; import { Empty } from "../../../../../google/protobuf/empty"; /** - * Makes protobuf to forcefully compile all well-known types into code (for TS) - * or just add dependencies on them (for go) + * Forces protobuf to compile all well-known types into code (for TS) + * or just adds dependencies on them (for go) * * @generated from protobuf message MiLaboratories.PL._Stub._ImportWellKnown */ diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts index e906dbae72..7eee25319a 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/resource_types.ts @@ -137,6 +137,10 @@ export interface NotificationFilter_EventFilter { * @generated from protobuf field: optional bool resource_ready = 5 */ resourceReady?: boolean; + /** + * @generated from protobuf field: optional bool resource_recovered = 18 + */ + resourceRecovered?: boolean; /** * @generated from protobuf field: optional bool resource_duplicate = 6 */ @@ -485,6 +489,7 @@ class NotificationFilter_EventFilter$Type extends MessageType; export interface components { - schemas: { - AttachFilter_Request: { - subscriptionId: string; - filterName: string; - filterId: string; - }; - AttachFilter_Response: Record; - AttachSubscription_Request: { - controllerId: string; - subscriptionId: string; - }; - AttachSubscription_Response: Record; - ClearFeatures_Request: { - controllerType: string; - }; - ClearFeatures_Response: Record; - Create_Request: { - /** Format: enum */ - type: number; - /** @description field ID is always combination of parent resource ID and field name */ - id: components["schemas"]["FieldRef"]; - }; - Create_Response: { - globalId: components["schemas"]["FieldRef"]; - }; - Deregister_Request: { - controllerType: string; - }; - Deregister_Response: Record; - DetachFilter_Request: { - subscriptionId: string; - filterName: string; - }; - DetachFilter_Response: Record; - Exists_Request: { - resourceId: string; - }; - Exists_Response: { - exists: boolean; - }; - Field: { - /** @description field ID is always combination of parent resource ID and field name */ - id: components["schemas"]["FieldRef"]; - /** Format: enum */ - type: number; - features: components["schemas"]["Resource_Features"]; - /** - * @description _resolved_ value of field or _assigned_ if the field was assigned to a resource. - * If field refers to another field, it will get - * value only when this chain of references ends up with direct resource - * reference. At that moment all fields in the chain will get their values - * resolved and will start to refer to the same resource directly. - */ - value: string; - /** - * Format: enum - * @description If the value was empty, assigned or finally resolved. - */ - valueStatus: number; - /** @description If the value is in its final state (ready, duplicate or error) */ - valueIsFinal: boolean; - /** - * @description Resource error resource id if any. - * Is intended to report problems _from_ platform to client. - */ - error: string; - }; - FieldRef: { - resourceId: string; - fieldName: string; - }; - FieldSchema: { - /** Format: enum */ - type: number; - name: string; - }; - GetJWTToken_Request: { - expiration: string; - }; - GetJWTToken_Response: { - token: string; - }; - GetNotifications_Request: { - controllerType: string; - /** Format: uint32 */ - maxNotifications: number; - }; - GetNotifications_Response: { - notifications: components["schemas"]["Notification"][]; - }; - GetUrl_Request: { - controllerAlias: string; - resourceId: string; - }; - GetUrl_Response: { - controllerUrl: string; - }; - Get_Request: { - resourceId: string; - loadFields: boolean; - }; - Get_Response: { - resource: components["schemas"]["Resource"]; - }; - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - License_Response: { - /** Format: int32 */ - status: number; - isOk: boolean; - /** - * Format: bytes - * @description Raw response body as it was received from the license server. - */ - responseBody: string; - }; - ListMethods_MethodInfo: { - type: string; - name: string; - info: { - [key: string]: string; - }; - }; - ListMethods_Response: { - methods: components["schemas"]["ListMethods_MethodInfo"][]; - }; - ListResourceTypes_Response: { - types: components["schemas"]["ResourceType"][]; - }; - Notification: { - subscriptionId: string; - eventId: string; - resourceId: string; - resourceType: components["schemas"]["ResourceType"]; - events: components["schemas"]["Notification_Events"]; - fieldChanges: { - [key: string]: components["schemas"]["Notification_FieldChange"]; - }; - payload: components["schemas"]["NotificationFilter_Payload"]; - filterName: string; - txSpan: components["schemas"]["SpanInfo"]; - }; - NotificationFilter: { - resourceType: components["schemas"]["ResourceType"]; - resourceId: string; - eventFilter: components["schemas"]["NotificationFilter_EventFilter"]; - payload: components["schemas"]["NotificationFilter_Payload"]; - }; - NotificationFilter_EventFilter: { - all: boolean; - resourceCreated: boolean; - resourceDeleted: boolean; - resourceReady: boolean; - resourceDuplicate: boolean; - resourceError: boolean; - /** @description Field events */ - inputsLocked: boolean; - outputsLocked: boolean; - fieldCreated: boolean; - fieldGotError: boolean; - inputSet: boolean; - allInputsSet: boolean; - outputSet: boolean; - allOutputsSet: boolean; - genericOtwSet: boolean; - dynamicChanged: boolean; - }; - NotificationFilter_Payload: { - values: { - [key: string]: string; - }; - }; - Notification_Events: { - resourceCreated: boolean; - resourceDeleted: boolean; - resourceReady: boolean; - resourceDuplicate: boolean; - resourceError: boolean; - inputsLocked: boolean; - outputsLocked: boolean; - fieldCreated: boolean; - fieldGotError: boolean; - inputSet: boolean; - allInputsSet: boolean; - outputSet: boolean; - allOutputsSet: boolean; - genericOtwSet: boolean; - dynamicChanged: boolean; - }; - Notification_FieldChange: { - old: components["schemas"]["Field"]; - new: components["schemas"]["Field"]; - }; - Ping_Response: { - coreVersion: string; - coreFullVersion: string; - serverInfo: string; - /** Format: enum */ - compression: number; - /** - * @description instanceID is a unique ID that changes when we reset DB state. - * If we reset a state and a database, but the address of the backend is still the same, - * without instanceID we are not sure if it's the same state or not, - * and UI can't detect it and clear its state (e.g. caches of drivers). - */ - instanceId: string; - platform: string; - os: string; - arch: string; - }; - Register_Request: { - controllerType: string; - filters: { - [key: string]: components["schemas"]["NotificationFilter"]; - }; - resourceSchemas: components["schemas"]["ResourceSchema"][]; - }; - Register_Response: { - controllerId: string; - subscriptionId: string; - }; - Release_Request: { - resourceId: string; - /** Format: bytes */ - leaseId: string; - }; - Release_Response: Record; - RemoveAliasesAndUrls_Request: { - controllerType: string; - }; - RemoveAliasesAndUrls_Response: Record; - Resource: { - id: string; - /** Format: bytes */ - canonicalId: string; - /** Format: enum */ - kind: number; - type: components["schemas"]["ResourceType"]; - /** Format: bytes */ - data: string; - features: components["schemas"]["Resource_Features"]; - fields: components["schemas"]["Field"][]; - /** @description Resource has at least one field with error */ - hasErrors: boolean; - inputsLocked: boolean; - outputsLocked: boolean; - resourceReady: boolean; - isFinal: boolean; - originalResourceId: string; - parentResourceId: string; - /** Format: date-time */ - createdTime: string; - /** Format: date-time */ - deletedTime: string; - }; - ResourceAPIFeature: { - controllerType: string; - featureName: string; - resourceType: components["schemas"]["ResourceType"]; - endpoint: string; - }; - ResourceSchema: { - type: components["schemas"]["ResourceType"]; - fields: components["schemas"]["FieldSchema"][]; - }; - ResourceType: { - name: string; - version: string; - }; - Resource_Features: { - ephemeral: boolean; - }; - SetFeatures_Request: { - features: components["schemas"]["ResourceAPIFeature"][]; - }; - SetFeatures_Response: Record; - SpanInfo: { - path: string; - carrier: { - [key: string]: string; - }; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; - }; - Sync_Request: { - txId: string; - }; - Sync_Response: Record; - Update_Request: { - controllerType: string; - filters: { - [key: string]: components["schemas"]["NotificationFilter"]; - }; - resourceSchemas: components["schemas"]["ResourceSchema"][]; - }; - Update_Response: Record; - WriteAliasesAndUrls_Request: { - controllerType: string; - aliasesToUrls: { - [key: string]: string; - }; - }; - WriteAliasesAndUrls_Response: Record; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + schemas: { + AttachFilter_Request: { + subscriptionId: string; + filterName: string; + filterId: string; + }; + AttachFilter_Response: Record; + AttachSubscription_Request: { + controllerId: string; + subscriptionId: string; + }; + AttachSubscription_Response: Record; + ClearFeatures_Request: { + controllerType: string; + }; + ClearFeatures_Response: Record; + Create_Request: { + /** Format: enum */ + type: number; + /** @description field ID is always combination of parent resource ID and field name */ + id: components["schemas"]["FieldRef"]; + }; + Create_Response: { + globalId: components["schemas"]["FieldRef"]; + }; + Deregister_Request: { + controllerType: string; + }; + Deregister_Response: Record; + DetachFilter_Request: { + subscriptionId: string; + filterName: string; + }; + DetachFilter_Response: Record; + Exists_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + }; + Exists_Response: { + exists: boolean; + }; + Field: { + /** @description field ID is always combination of parent resource ID and field name */ + id: components["schemas"]["FieldRef"]; + /** Format: enum */ + type: number; + features: components["schemas"]["Resource_Features"]; + /** + * @description _resolved_ value of a field or _assigned_ if the field was assigned to a resource. + * If a field refers to another field, it will get + * a value only when this chain of references ends up with a direct resource + * reference. At that moment, all fields in the chain will get their values + * resolved and will start to refer to the same resource directly. + */ + value: string; + /** + * Format: bytes + * @description Signature for value resource ID, inheriting the parent resource's color. + * Populated server-side when the parent resource has a known color in the current TX. + */ + valueSignature: string; + /** + * Format: enum + * @description Whether the value is empty, assigned, or finally resolved. + */ + valueStatus: number; + /** @description If the value is in its final state (ready, duplicate or error) */ + valueIsFinal: boolean; + /** + * @description Error resource ID, if any. + * Is intended to report problems _from_ the platform to the client. + */ + error: string; + /** + * Format: bytes + * @description Signature for error resource ID, inheriting the parent resource's color. + */ + errorSignature: string; + }; + FieldRef: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + fieldName: string; + }; + FieldSchema: { + /** Format: enum */ + type: number; + name: string; + }; + GetJWTToken_Request: { + expiration: string; + /** Format: enum */ + requestedRole: number; + }; + GetJWTToken_Response: { + token: string; + /** Format: bytes */ + sessionId: string; + }; + GetNotifications_Request: { + controllerType: string; + /** Format: uint32 */ + maxNotifications: number; + }; + GetNotifications_Response: { + notifications: components["schemas"]["Notification"][]; + }; + GetUrl_Request: { + controllerAlias: string; + resourceId: string; + }; + GetUrl_Response: { + controllerUrl: string; + }; + Get_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + loadFields: boolean; + }; + Get_Response: { + resource: components["schemas"]["Resource"]; + }; + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; + }; + GrantAccess_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + targetUser: string; + permissions: components["schemas"]["Grant_Permissions"]; + }; + GrantAccess_Response: Record; + /** @description Permissions describes access level for a grant. */ + Grant_Permissions: { + writable: boolean; + }; + License_Response: { + /** Format: int32 */ + status: number; + isOk: boolean; + /** + * Format: bytes + * @description Raw response body as it was received from the license server. + */ + responseBody: string; + }; + ListMethods_MethodInfo: { + type: string; + name: string; + info: { + [key: string]: string; + }; + }; + ListMethods_Response: { + methods: components["schemas"]["ListMethods_MethodInfo"][]; + }; + ListResourceTypes_Response: { + types: components["schemas"]["ResourceType"][]; + }; + Notification: { + subscriptionId: string; + eventId: string; + resourceId: string; + resourceType: components["schemas"]["ResourceType"]; + events: components["schemas"]["Notification_Events"]; + fieldChanges: { + [key: string]: components["schemas"]["Notification_FieldChange"]; + }; + payload: components["schemas"]["NotificationFilter_Payload"]; + filterName: string; + txSpan: components["schemas"]["SpanInfo"]; + }; + NotificationFilter: { + resourceType: components["schemas"]["ResourceType"]; + resourceId: string; + eventFilter: components["schemas"]["NotificationFilter_EventFilter"]; + payload: components["schemas"]["NotificationFilter_Payload"]; + }; + NotificationFilter_EventFilter: { + all: boolean; + resourceCreated: boolean; + resourceDeleted: boolean; + resourceReady: boolean; + resourceRecovered: boolean; + resourceDuplicate: boolean; + resourceError: boolean; + /** @description Field events */ + inputsLocked: boolean; + outputsLocked: boolean; + fieldCreated: boolean; + fieldGotError: boolean; + inputSet: boolean; + allInputsSet: boolean; + outputSet: boolean; + allOutputsSet: boolean; + genericOtwSet: boolean; + dynamicChanged: boolean; + }; + NotificationFilter_Payload: { + values: { + [key: string]: string; + }; + }; + Notification_Events: { + resourceCreated: boolean; + resourceDeleted: boolean; + resourceReady: boolean; + resourceDuplicate: boolean; + resourceError: boolean; + inputsLocked: boolean; + outputsLocked: boolean; + fieldCreated: boolean; + fieldGotError: boolean; + inputSet: boolean; + allInputsSet: boolean; + outputSet: boolean; + allOutputsSet: boolean; + genericOtwSet: boolean; + dynamicChanged: boolean; + resourceRecovered: boolean; + }; + Notification_FieldChange: { + old: components["schemas"]["Field"]; + new: components["schemas"]["Field"]; + }; + Ping_Response: { + coreVersion: string; + coreFullVersion: string; + /** Format: enum */ + compression: number; + /** + * @description instanceID is a unique ID that changes when we reset DB state. + * If we reset a state and a database, but the address of the backend is still the same, + * without instanceID we are not sure if it's the same state or not, + * and UI can't detect it and clear its state (e.g. caches of drivers). + */ + instanceId: string; + platform: string; + os: string; + arch: string; + }; + Register_Request: { + controllerType: string; + filters: { + [key: string]: components["schemas"]["NotificationFilter"]; + }; + resourceSchemas: components["schemas"]["ResourceSchema"][]; + }; + Register_Response: { + controllerId: string; + subscriptionId: string; + }; + Release_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + /** Format: bytes */ + leaseId: string; + }; + Release_Response: Record; + RemoveAliasesAndUrls_Request: { + controllerType: string; + }; + RemoveAliasesAndUrls_Response: Record; + Resource: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + /** Format: bytes */ + canonicalId: string; + /** Format: enum */ + kind: number; + type: components["schemas"]["ResourceType"]; + /** Format: bytes */ + data: string; + features: components["schemas"]["Resource_Features"]; + fields: components["schemas"]["Field"][]; + /** @description Resource has at least one field with error */ + hasErrors: boolean; + inputsLocked: boolean; + outputsLocked: boolean; + resourceReady: boolean; + isFinal: boolean; + originalResourceId: string; + parentResourceId: string; + /** Format: date-time */ + createdTime: string; + /** Format: date-time */ + deletedTime: string; + }; + ResourceAPIFeature: { + controllerType: string; + featureName: string; + resourceType: components["schemas"]["ResourceType"]; + endpoint: string; + }; + ResourceSchema: { + type: components["schemas"]["ResourceType"]; + fields: components["schemas"]["FieldSchema"][]; + /** @description Access restriction flags for non-controller roles */ + accessFlags: components["schemas"]["ResourceSchema_AccessFlags"]; + freeInputs: boolean; + freeOutputs: boolean; + }; + ResourceSchema_AccessFlags: { + /** + * @description Deny-list approach: default = allowed (true) + * Controllers set these to false to restrict non-controller roles (role='u', role='w') + */ + createResource: boolean; + /** @description IMPORTANT: read_fields=false with write_fields=true is a forbidden combination */ + readFields: boolean; + writeFields: boolean; + /** @description IMPORTANT: read_kv=false with write_kv=true is a forbidden combination */ + readKv: boolean; + writeKv: boolean; + /** + * @description Per-field-type overrides (map: field_type → bool) + * When defined for a field type, overrides resource-level flags + */ + readByFieldType: { + [key: string]: boolean; + }; + writeByFieldType: { + [key: string]: boolean; + }; + }; + ResourceType: { + name: string; + version: string; + }; + Resource_Features: { + ephemeral: boolean; + }; + RevokeGrant_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + targetUser: string; + }; + RevokeGrant_Response: Record; + SetFeatures_Request: { + features: components["schemas"]["ResourceAPIFeature"][]; + }; + SetFeatures_Response: Record; + SpanInfo: { + path: string; + carrier: { + [key: string]: string; + }; + }; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; + }; + Sync_Request: { + txId: string; + }; + Sync_Response: Record; + Update_Request: { + controllerType: string; + filters: { + [key: string]: components["schemas"]["NotificationFilter"]; + }; + resourceSchemas: components["schemas"]["ResourceSchema"][]; + }; + Update_Response: Record; + WriteAliasesAndUrls_Request: { + controllerType: string; + aliasesToUrls: { + [key: string]: string; + }; + }; + WriteAliasesAndUrls_Response: Record; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - Platform_GetJWTToken: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetJWTToken_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetJWTToken_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_AuthMethods: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListMethods_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_WriteControllerAliasesAndUrls: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["WriteAliasesAndUrls_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["WriteAliasesAndUrls_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_RemoveControllerAliasesAndUrls: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["RemoveAliasesAndUrls_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["RemoveAliasesAndUrls_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerAttachSubscription: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["AttachSubscription_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AttachSubscription_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerCreate: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Create_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Create_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerDeregister: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Deregister_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Deregister_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerExists: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Exists_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Exists_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerSetFeatures: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["SetFeatures_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["SetFeatures_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerClearFeatures: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["ClearFeatures_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ClearFeatures_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerGet: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Get_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Get_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_GetControllerNotifications: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetNotifications_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetNotifications_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerRegister: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Register_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Register_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ControllerUpdate: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Update_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Update_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_GetControllerUrl: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetUrl_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetUrl_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_License: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["License_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_UpdateLease: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Update_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Update_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_LeaseResource: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Create_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Create_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ReleaseLease: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Release_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Release_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_NotificationsGet: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Get_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Get_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_Ping: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Ping_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_ListResourceTypes: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody?: never; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["ListResourceTypes_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_SubscriptionAttachFilter: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["AttachFilter_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["AttachFilter_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_SubscriptionDetachFilter: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["DetachFilter_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["DetachFilter_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Platform_TxSync: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Sync_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Sync_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Status"]; + Platform_GrantAccess: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GrantAccess_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GrantAccess_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_GetJWTToken: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GetJWTToken_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetJWTToken_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_AuthMethods: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListMethods_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_RevokeGrant: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RevokeGrant_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RevokeGrant_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_WriteControllerAliasesAndUrls: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["WriteAliasesAndUrls_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["WriteAliasesAndUrls_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_RemoveControllerAliasesAndUrls: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["RemoveAliasesAndUrls_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RemoveAliasesAndUrls_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerAttachSubscription: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AttachSubscription_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AttachSubscription_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerCreate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Create_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Create_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerDeregister: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Deregister_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Deregister_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerExists: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Exists_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Exists_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerSetFeatures: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["SetFeatures_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["SetFeatures_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerClearFeatures: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["ClearFeatures_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ClearFeatures_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerGet: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Get_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Get_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_GetControllerNotifications: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GetNotifications_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetNotifications_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerRegister: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Register_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Register_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ControllerUpdate: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Update_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Update_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_GetControllerUrl: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["GetUrl_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetUrl_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_License: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["License_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_LeaseResource: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Create_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Create_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ReleaseLease: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Release_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Release_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_UpdateLease: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Update_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Update_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_LockFieldValues: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Create_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Create_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_NotificationsGet: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Get_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Get_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_Ping: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Ping_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_ListResourceTypes: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody?: never; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["ListResourceTypes_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_SubscriptionAttachFilter: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["AttachFilter_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["AttachFilter_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_SubscriptionDetachFilter: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["DetachFilter_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["DetachFilter_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Platform_TxSync: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Sync_Request"]; + }; + }; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Sync_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } diff --git a/lib/node/pl-drivers/proto/shared/downloadapi/openapi.yaml b/lib/node/pl-drivers/proto/shared/downloadapi/openapi.yaml index 4dea8b3b0a..da35188ae9 100644 --- a/lib/node/pl-drivers/proto/shared/downloadapi/openapi.yaml +++ b/lib/node/pl-drivers/proto/shared/downloadapi/openapi.yaml @@ -3,86 +3,90 @@ openapi: 3.0.3 info: - title: Download API - description: Download provides access to any data, that can be downloaded via network. - version: 0.0.1 + title: Download API + description: Download provides access to any data that can be downloaded over the network. + version: 0.0.1 paths: - /v1/get-download-url: - post: - tags: - - Download - operationId: Download_GetDownloadURL - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetDownloadURL_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetDownloadURL_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/get-download-url: + post: + tags: + - Download + operationId: Download_GetDownloadURL + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetDownloadURL_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetDownloadURL_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - GetDownloadURL_HTTPHeader: - type: object - properties: - name: - type: string - value: - type: string - GetDownloadURL_Request: - type: object - properties: - resourceId: - type: string - isInternalUse: - type: boolean - description: |- - Pass `true` here if the blob will be downloaded from internal network, - e.g. controllers could use this if they are trying to download something from internal network. - For backward compatibility, by default pl treats all requests as from external network. - GetDownloadURL_Response: - type: object - properties: - downloadUrl: - type: string - headers: - type: array - items: - $ref: "#/components/schemas/GetDownloadURL_HTTPHeader" - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." + schemas: + GetDownloadURL_HTTPHeader: + type: object + properties: + name: + type: string + value: + type: string + GetDownloadURL_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + isInternalUse: + type: boolean + description: |- + Pass `true` here if the blob will be downloaded from the internal network, + e.g. controllers could use this if they are trying to download something from the internal network. + For backward compatibility, by default pl treats all requests as from the external network. + GetDownloadURL_Response: + type: object + properties: + downloadUrl: + type: string + headers: + type: array + items: + $ref: '#/components/schemas/GetDownloadURL_HTTPHeader' + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' tags: - - name: Download + - name: Download diff --git a/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto b/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto index 552975763b..0493740d67 100644 --- a/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto @@ -7,7 +7,7 @@ option go_package = "github.com/milaboratory/pl/controllers/shared/grpc/download import "github.com/googleapis/googleapis/google/api/annotations.proto"; // -// Download provides access to any data, that can be downloaded via network. +// Download provides access to any data that can be downloaded over the network. // service Download { rpc GetDownloadURL(DownloadAPI.GetDownloadURL.Request) returns (DownloadAPI.GetDownloadURL.Response) { @@ -22,10 +22,12 @@ message DownloadAPI { message GetDownloadURL { message Request { uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 3; - // Pass `true` here if the blob will be downloaded from internal network, - // e.g. controllers could use this if they are trying to download something from internal network. - // For backward compatibility, by default pl treats all requests as from external network. + // Pass `true` here if the blob will be downloaded from the internal network, + // e.g. controllers could use this if they are trying to download something from the internal network. + // For backward compatibility, by default pl treats all requests as from the external network. bool is_internal_use = 2; } diff --git a/lib/node/pl-drivers/proto/shared/lsapi/openapi.yaml b/lib/node/pl-drivers/proto/shared/lsapi/openapi.yaml index 4ca4c37129..9e24c69678 100644 --- a/lib/node/pl-drivers/proto/shared/lsapi/openapi.yaml +++ b/lib/node/pl-drivers/proto/shared/lsapi/openapi.yaml @@ -3,130 +3,134 @@ openapi: 3.0.3 info: - title: LS API - description: |- - LS provides access to lists of blobs (files, S3 objects and so on) is some storage. - This API allows clients to know, whan items are available in storages, that - the contorllers can access, providing clients with the ability to start indexation - on, say, existing sequence files from the corporate storage. - version: 0.0.1 + title: LS API + description: |- + LS provides access to lists of blobs (files, S3 objects and so on) in some storage. + This API allows clients to know what items are available in storages that + the controllers can access, providing clients with the ability to start indexing, + say, existing sequence files from the corporate storage. + version: 0.0.1 paths: - /v1/list: - post: - tags: - - LS - operationId: LS_List - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/List_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/List_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/list: + post: + tags: + - LS + operationId: LS_List + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/List_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/List_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - List_Request: - type: object - properties: - resourceId: - type: string - description: resource_id of 'LS/' resource - location: - type: string - description: |- - location to list, absolute to storage root. Only items, that have starting - from are included into list response. - List_Response: - type: object - properties: - items: - type: array - items: - $ref: "#/components/schemas/LsAPI_ListItem" - description: |- - List of the full (absolute to storage root) names of items from storage. - E.g., for 'fs' storage each name will consist of names of all directories, where the - item is located, and the item name itself. - The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. - delimiter: - type: string - description: |- - delimiter is path separator, used in this storage. Client can use it to parse item names into parts, - to extract directory names. - LsAPI_ListItem: - type: object - properties: - name: - type: string - description: name of the item in storage, without any prefixes - size: - type: string - description: |- - size of item in bytes - is always zero for directories (is_dir = true) - isDir: - type: boolean - description: is_dir is true for item, that can have subitems. - fullName: - type: string - description: |- - full_name is the name of item absolute to storage root. - it is + - The , used in names, is storage-specific and is NOT guaranteed to be '/'. - directory: - type: string - description: |- - directory, the item is located in. The value here is always a prefix of name: - name.HasPrefix(directory) is always true. - lastModified: - type: string - description: last_modified keeps the item last modification timestamp - format: date-time - version: - type: string - description: |- - version of item in storage. - When storage supports versioning or provides checksums for the data stored, - the field keeps that data. - If not - it keeps the any simple combination of item attributes, that helps to - detect if the contents of item has changed, e.g. +. - Anyway, client should not try to interpret this field, but should provide it to the Platform - in operations with given item (like BlobImportInternal) to help Platform with deduplication. - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + List_Request: + type: object + properties: + resourceId: + type: string + description: resource_id of 'LS/' resource + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + location: + type: string + description: |- + Location to list, relative to the storage root. Only items that have starting + with are included in the list response. + List_Response: + type: object + properties: + items: + type: array + items: + $ref: '#/components/schemas/LsAPI_ListItem' + description: |- + List of the full (relative to storage root) names of items from storage. + E.g., for 'fs' storage each name will consist of names of all directories, where the + item is located, and the item name itself. + The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. + delimiter: + type: string + description: |- + The delimiter is the path separator used in this storage. The client can use it to parse item names into parts, + to extract directory names. + LsAPI_ListItem: + type: object + properties: + name: + type: string + description: Name of the item in storage, without any prefixes. + size: + type: string + description: |- + Size of the item in bytes. + Is always zero for directories (is_dir = true). + isDir: + type: boolean + description: is_dir is true for an item that can have subitems. + fullName: + type: string + description: |- + full_name is the full name of the item, relative to the storage root. + It is + . + The , used in names, is storage-specific and is NOT guaranteed to be '/'. + directory: + type: string + description: |- + The directory the item is located in. The value here is always a prefix of name: + name.HasPrefix(directory) is always true. + lastModified: + type: string + description: last_modified keeps the item's last modification timestamp. + format: date-time + version: + type: string + description: |- + Version of item in storage. + When the storage supports versioning or provides checksums for the data stored, + the field keeps that data. + If not, it keeps any simple combination of item attributes that helps to + detect if the contents of the item have changed, e.g. +. + Anyway, the client should not try to interpret this field, but should provide it to the Platform + in operations with a given item (like BlobImportInternal) to help the Platform with deduplication. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' tags: - - name: LS + - name: LS diff --git a/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto b/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto index 840497d580..2d9d2b7225 100644 --- a/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto @@ -8,10 +8,10 @@ import "google/protobuf/timestamp.proto"; import "github.com/googleapis/googleapis/google/api/annotations.proto"; // -// LS provides access to lists of blobs (files, S3 objects and so on) is some storage. -// This API allows clients to know, whan items are available in storages, that -// the contorllers can access, providing clients with the ability to start indexation -// on, say, existing sequence files from the corporate storage. +// LS provides access to lists of blobs (files, S3 objects and so on) in some storage. +// This API allows clients to know what items are available in storages that +// the controllers can access, providing clients with the ability to start indexing, +// say, existing sequence files from the corporate storage. // service LS { rpc List(LsAPI.List.Request) returns (LsAPI.List.Response) { @@ -24,35 +24,35 @@ service LS { message LsAPI { message ListItem { - // name of the item in storage, without any prefixes + // Name of the item in storage, without any prefixes. string name = 1; - // size of item in bytes - // is always zero for directories (is_dir = true) + // Size of the item in bytes. + // Is always zero for directories (is_dir = true). uint64 size = 2; - // is_dir is true for item, that can have subitems. + // is_dir is true for an item that can have subitems. bool is_dir = 3; - // full_name is the name of item absolute to storage root. - // it is + + // full_name is the full name of the item, relative to the storage root. + // It is + . // The , used in names, is storage-specific and is NOT guaranteed to be '/'. string full_name = 10; - // directory, the item is located in. The value here is always a prefix of name: + // The directory the item is located in. The value here is always a prefix of name: // name.HasPrefix(directory) is always true. string directory = 11; - // last_modified keeps the item last modification timestamp + // last_modified keeps the item's last modification timestamp. google.protobuf.Timestamp last_modified = 12; - // version of item in storage. - // When storage supports versioning or provides checksums for the data stored, + // Version of item in storage. + // When the storage supports versioning or provides checksums for the data stored, // the field keeps that data. - // If not - it keeps the any simple combination of item attributes, that helps to - // detect if the contents of item has changed, e.g. +. - // Anyway, client should not try to interpret this field, but should provide it to the Platform - // in operations with given item (like BlobImportInternal) to help Platform with deduplication. + // If not, it keeps any simple combination of item attributes that helps to + // detect if the contents of the item have changed, e.g. +. + // Anyway, the client should not try to interpret this field, but should provide it to the Platform + // in operations with a given item (like BlobImportInternal) to help the Platform with deduplication. string version = 13; } @@ -60,9 +60,11 @@ message LsAPI { message Request { // resource_id of 'LS/' resource uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 3; - // location to list, absolute to storage root. Only items, that have starting - // from are included into list response. + // Location to list, relative to the storage root. Only items that have starting + // with are included in the list response. string location = 2; // // limit amount of items returned by server in single response. @@ -87,13 +89,13 @@ message LsAPI { } message Response { - // List of the full (absolute to storage root) names of items from storage. + // List of the full (relative to storage root) names of items from storage. // E.g., for 'fs' storage each name will consist of names of all directories, where the // item is located, and the item name itself. // The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. repeated ListItem items = 1; - // delimiter is path separator, used in this storage. Client can use it to parse item names into parts, + // The delimiter is the path separator used in this storage. The client can use it to parse item names into parts, // to extract directory names. string delimiter = 2; diff --git a/lib/node/pl-drivers/proto/shared/progressapi/openapi.yaml b/lib/node/pl-drivers/proto/shared/progressapi/openapi.yaml index 2b97d70350..54ba01bb55 100644 --- a/lib/node/pl-drivers/proto/shared/progressapi/openapi.yaml +++ b/lib/node/pl-drivers/proto/shared/progressapi/openapi.yaml @@ -3,121 +3,127 @@ openapi: 3.0.3 info: - title: Progress API - description: Progress provides access to progress of any long-running process associated with resource. - version: 0.0.1 + title: Progress API + description: Progress provides access to the progress of any long-running process associated with a resource. + version: 0.0.1 paths: - /v1/get-progress: - post: - tags: - - Progress - operationId: Progress_GetStatus - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetStatus_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetStatus_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/realtime-progress: - post: - tags: - - Progress - operationId: Progress_RealtimeStatus - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/RealtimeStatus_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/RealtimeStatus_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/get-progress: + post: + tags: + - Progress + operationId: Progress_GetStatus + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetStatus_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetStatus_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/realtime-progress: + post: + tags: + - Progress + operationId: Progress_RealtimeStatus + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/RealtimeStatus_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/RealtimeStatus_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - GetStatus_Request: - type: object - properties: - resourceId: - type: string - GetStatus_Response: - type: object - properties: - report: - $ref: "#/components/schemas/ProgressAPI_Report" - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - ProgressAPI_Report: - type: object - properties: - progress: - type: number - format: float - bytesProcessed: - type: string - bytesTotal: - type: string - done: - type: boolean - name: - type: string - description: Name of current progress stage (if any) - RealtimeStatus_Request: - type: object - properties: - resourceId: - type: string - updateInterval: - pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ - type: string - RealtimeStatus_Response: - type: object - properties: - report: - $ref: "#/components/schemas/ProgressAPI_Report" - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." + schemas: + GetStatus_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + GetStatus_Response: + type: object + properties: + report: + $ref: '#/components/schemas/ProgressAPI_Report' + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + ProgressAPI_Report: + type: object + properties: + progress: + type: number + format: float + bytesProcessed: + type: string + bytesTotal: + type: string + done: + type: boolean + name: + type: string + description: Name of current progress stage (if any) + RealtimeStatus_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + format: bytes + updateInterval: + pattern: ^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$ + type: string + RealtimeStatus_Response: + type: object + properties: + report: + $ref: '#/components/schemas/ProgressAPI_Report' + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' tags: - - name: Progress + - name: Progress diff --git a/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto b/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto index 6e3a41d3b9..1c8b0ec62f 100644 --- a/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto @@ -8,7 +8,7 @@ import "google/protobuf/duration.proto"; import "github.com/googleapis/googleapis/google/api/annotations.proto"; // -// Progress provides access to progress of any long-running process associated with resource. +// Progress provides access to the progress of any long-running process associated with a resource. // service Progress { rpc GetStatus(ProgressAPI.GetStatus.Request) returns (ProgressAPI.GetStatus.Response) { @@ -40,6 +40,7 @@ message ProgressAPI { message GetStatus { message Request { uint64 resource_id = 1; + bytes resource_signature = 2; } message Response { @@ -50,6 +51,7 @@ message ProgressAPI { message RealtimeStatus { message Request { uint64 resource_id = 1; + bytes resource_signature = 3; google.protobuf.Duration update_interval = 2; } diff --git a/lib/node/pl-drivers/proto/shared/protodep.toml b/lib/node/pl-drivers/proto/shared/protodep.toml index ad71c2f13e..5974d92a4d 100644 --- a/lib/node/pl-drivers/proto/shared/protodep.toml +++ b/lib/node/pl-drivers/proto/shared/protodep.toml @@ -1,5 +1,5 @@ proto_outdir = "./.proto" [[dependencies]] -target = "github.com/googleapis/googleapis" -path = "./" + target = "github.com/googleapis/googleapis" + path = "./" diff --git a/lib/node/pl-drivers/proto/shared/streamingapi/openapi.yaml b/lib/node/pl-drivers/proto/shared/streamingapi/openapi.yaml index f25ae4e626..c061e08230 100644 --- a/lib/node/pl-drivers/proto/shared/streamingapi/openapi.yaml +++ b/lib/node/pl-drivers/proto/shared/streamingapi/openapi.yaml @@ -3,318 +3,247 @@ openapi: 3.0.3 info: - title: Streaming API - description: |- - Streaming provides access to online data stream from item in storage. Whenever item is appended with data, - the caller receives this fresh data in stream from server. - version: 0.0.1 + title: Streaming API + description: |- + Streaming provides access to an online data stream from an item in storage. Whenever data is appended to an item, + the caller receives this fresh data in a stream from the server. + version: 0.0.1 paths: - /v1/last-lines: - post: - tags: - - Streaming - description: "LastLines provides single message with the last lines from data source.\n When search pattern is specified, the last lines matching the given pattern are returned.\n The lines are returned in reversed order, as server reads data source from the end.\n Consider it as equivalent to 'tac | grep | head -n '\n The returned in the response points to the _beginning_ of the last \n line found, so client can continue reading the file backwards in subsequent calls.\n This means, that use of this in ReadText() will return you the same line\n returned last in LastLines() data." - operationId: Streaming_LastLines - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_LastLines" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/read/binary: - post: - tags: - - Streaming - description: "ReadBinary allows to read remote item in chunks using stream-like API.\n The difference to StreamBinary is that the client receives single response for each \n call and has to send new calls to the server to get fresh data from remote item.\n Each response (each chunk from server) keeps not more than 3.9MiB of data." - operationId: Streaming_ReadBinary - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_ReadBinary" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/read/text: - post: - tags: - - Streaming - description: "ReadBinary allows to read remote item in chunks using stream-like API.\n The difference to StreamBinary is that the client receives single response for each \n call and has to send new calls to the server to get fresh data from remote item.\n Each response (each chunk from server) keeps not more than 3.9MiB of data." - operationId: Streaming_ReadText - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_ReadText" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/stream/binary: - post: - tags: - - Streaming - description: |- - StreamBinary provides stream of binary file. Each response message keeps - one single chunk of binary data from data source. See StreamingAPI.Binary message - for more info on available options. - operationId: Streaming_StreamBinary - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_StreamBinary" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/stream/text: - post: - tags: - - Streaming - description: |- - StreamText provides stream of textual file, splitting the data by newline symbol. - Each response message keeps one single line of text from data source. - operationId: Streaming_StreamText - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_StreamText" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/StreamingAPI_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/last-lines: + post: + tags: + - Streaming + description: |- + LastLines provides a single message with the last lines from the data source. + When a search pattern is specified, the last lines matching the given pattern are returned. + The lines are returned in reverse order, as the server reads the data source from the end. + Consider it equivalent to 'tac | grep | head -n ' + The returned in the response points to the _beginning_ of the last + line found, so the client can continue reading the file backwards in subsequent calls. + This means that the use of this in ReadText() will return the last line + returned in the LastLines() response. + operationId: Streaming_LastLines + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_LastLines' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/read/binary: + post: + tags: + - Streaming + description: |- + ReadBinary allows reading a remote item in chunks using a stream-like API. + The client receives a single response for each call and has to send new calls + to the server to get fresh data from the remote item. + Each response (each chunk from the server) contains no more than 3.9MiB of data. + operationId: Streaming_ReadBinary + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_ReadBinary' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/read/text: + post: + tags: + - Streaming + description: |- + ReadText allows reading a remote item in chunks using a stream-like API. + The client receives a single response for each call and has to send new calls + to the server to get fresh data from the remote item. + Each response (each chunk from the server) contains no more than 3.9MiB of data. + operationId: Streaming_ReadText + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_ReadText' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/StreamingAPI_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." - StreamingAPI_LastLines: - type: object - properties: - resourceId: - type: string - description: of Stream resource, that keeps info on item to be streamed. - offset: - type: string - description: " makes streamer to perform seek operation to given offset before sending the contents.\n This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart.\n By default, LastLines starts to treat the data source from the very last byte available in data stream \n at the moment of call, but client can set the server to start from earlier position." - lineCount: - type: integer - description: |- - makes streamer to return up to lines to the client. - Default value: 1 - format: int32 - search: - type: string - description: |- - is substring for line search pattern. - This option makes controller to send to the client only lines, that - have given substring. - searchRe: - type: string - description: |- - is regular expression for line search pattern. - This option makes controller to send to the client only lines, that - match given regular expression. - StreamingAPI_ReadBinary: - type: object - properties: - resourceId: - type: string - description: of Stream resource, that keeps info on item to be streamed. - offset: - type: string - description: makes streamer to perform seek operation to given offset before sending the data. - chunkSize: - type: integer - description: |- - limits the maximum size of for response message in stream. + schemas: + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + StreamingAPI_LastLines: + type: object + properties: + resourceId: + type: string + description: of Stream resource that keeps info on item to be streamed. + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + offset: + type: string + description: |- + makes the streamer perform a seek operation to the given offset before sending the contents. + This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + By default, LastLines starts reading the data source from the very last byte available in the data stream + at the moment of the call, but the client can set the server to start from an earlier position. + lineCount: + type: integer + description: |- + makes the streamer return up to lines to the client. + Default value: 1 + format: int32 + search: + type: string + description: |- + is a substring for the line search pattern. + This option makes the controller send to the client only lines that + have the given substring. + searchRe: + type: string + description: |- + is a regular expression for the line search pattern. + This option makes the controller send to the client only lines that + match the given regular expression. + StreamingAPI_ReadBinary: + type: object + properties: + resourceId: + type: string + description: of Stream resource that keeps info on item to be streamed. + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + offset: + type: string + description: makes the streamer perform a seek operation to the given offset before sending the data. + chunkSize: + type: integer + description: |- + limits the maximum size of for response message in stream. - Default value: 32 768 (32 KiB) - Max value: 3900 * 1024 (3.9 MiB) - format: uint32 - StreamingAPI_ReadText: - type: object - properties: - resourceId: - type: string - description: of Stream resource, that keeps info on item to be streamed. - offset: - type: string - description: |- - makes streamer to perform seek operation to given offset before sending the contents. - This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - Client can just use the value of the last response from server to continue streaming after reconnection. - readLimit: - type: string - description: |- - allows client to limit total data sent from server. - Measured in lines of text. - E.g. to read top 1000 lines from stream source, use = 1000. - When both and / are set, the is applied first. - this is equivalent to 'head -n | grep '. - At most 3.9 MiB (3900 * 1024 KiB) of data is returned in single read regardless of option - Only full lines of text are returned except for the last line from the completed source - (the one that is not expected to have new data, like blob in storage) - search: - type: string - description: |- - is substring for line search pattern. - This option makes controller to send to the client only lines, that - have given substring. - searchRe: - type: string - description: |- - is regular expression for line search pattern. - This option makes controller to send to the client only lines, that - match given regular expression. - StreamingAPI_Response: - type: object - properties: - data: - type: string - description: data chunk from item, starting from the of the previous message in the same stream. - format: bytes - size: - type: string - description: " is the actual size of the streamed item at the moment of this message.\n This might be not a final amount of streamed data, as stream source can be updated \n by other independent process (e.g., data is written to log file).\n This field in combination with shows, how far the client is from the end\n of the data right now." - newOffset: - type: string - description: |- - is the new offset in bytes from the start of the streamed item, - including size of in current response. - Call to Stream rpc with = will continue - streaming from the place of last received message - (e.g. = - 1 will repeat the last byte of - previously received ) - StreamingAPI_StreamBinary: - type: object - properties: - resourceId: - type: string - description: of Stream resource, that keeps info on item to be streamed. - offset: - type: string - description: makes streamer to perform seek operation to given offset before sending the data. - chunkSize: - type: integer - description: |- - limits the maximum size of for each response message in stream. - - Default value: 32 768 (32 KiB) - Max value: 3900 * 1024 (3.9 MiB) - format: uint32 - readLimit: - type: string - description: |- - allows client to limit total data sent from server. - This limit is aggregation of all data, sent in all chunks. - E.g. to read 2000 bytes of data in chunks of at most - 130 bytes, use = 130; = 2000. - For storage item of appropriate size this settings will result in - 16 messages from server: 15 of 130 bytes and one of 50 bytes. - StreamingAPI_StreamText: - type: object - properties: - resourceId: - type: string - description: of Stream resource, that keeps info on item to be streamed. - offset: - type: string - description: |- - makes streamer to perform seek operation to given offset before sending the contents. - This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - Client can just use the value of the last response from server to continue streaming after reconnection. - readLimit: - type: string - description: " allows client to limit total data sent from server.\n This limit is aggregation of all data, sent in all chunks, measured \n in lines of text.\n E.g. to read top 1000 lines from stream source, use = 1000.\n When both and / are set, the is applied first.\n this is equivalent to 'head -n | grep '." - search: - type: string - description: |- - is substring for line search pattern. - This option makes controller to send to the client only lines, that - have given substring. - searchRe: - type: string - description: |- - is regular expression for line search pattern. - This option makes controller to send to the client only lines, that - match given regular expression. + Default value: 32 768 (32 KiB) + Max value: 3900 * 1024 (3.9 MiB) + format: uint32 + StreamingAPI_ReadText: + type: object + properties: + resourceId: + type: string + description: of Stream resource that keeps info on item to be streamed. + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + offset: + type: string + description: |- + makes the streamer perform a seek operation to the given offset before sending the contents. + This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + The client can just use the value of the last response from the server to continue streaming after reconnection. + readLimit: + type: string + description: |- + allows the client to limit total data sent from the server. + Measured in lines of text. + E.g. to read top 1000 lines from stream source, use = 1000. + When both and / are set, the is applied first. + This is equivalent to 'head -n | grep '. + At most 3.9 MiB (3900 KiB) of data is returned in a single read regardless of the option + Only full lines of text are returned except for the last line from the completed source + (the one that is not expected to have new data, like a blob in storage) + search: + type: string + description: |- + is a substring for the line search pattern. + This option makes the controller send to the client only lines that + have the given substring. + searchRe: + type: string + description: |- + is a regular expression for the line search pattern. + This option makes the controller send to the client only lines that + match the given regular expression. + StreamingAPI_Response: + type: object + properties: + data: + type: string + description: data chunk from the item, starting from the of the previous message in the same stream. + format: bytes + size: + type: string + description: |- + is the actual size of the streamed item at the moment of this message. + This might not be the final amount of streamed data, as the stream source can be updated + by another independent process (e.g., data is written to a log file). + This field in combination with shows how far the client is from the end + of the data right now. + newOffset: + type: string + description: |- + is the new offset in bytes from the start of the streamed item, + including the size of in the current response. + A call to the Stream RPC with = will continue + streaming from the place of the last received message + (e.g. = - 1 will repeat the last byte of + previously received ) tags: - - name: Streaming + - name: Streaming diff --git a/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto b/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto index 7eeb5a4f25..499d818f4b 100644 --- a/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto @@ -7,24 +7,14 @@ option go_package = "github.com/milaboratory/pl/controllers/shared/grpc/streamin import "github.com/googleapis/googleapis/google/api/annotations.proto"; // -// Streaming provides access to online data stream from item in storage. Whenever item is appended with data, -// the caller receives this fresh data in stream from server. +// Streaming provides access to an online data stream from an item in storage. Whenever data is appended to an item, +// the caller receives this fresh data in a stream from the server. // service Streaming { - // StreamBinary provides stream of binary file. Each response message keeps - // one single chunk of binary data from data source. See StreamingAPI.Binary message - // for more info on available options. - rpc StreamBinary(StreamingAPI.StreamBinary) returns (stream StreamingAPI.Response) { - option (google.api.http) = { - post: "/v1/stream/binary" - body: "*" - }; - } - - // ReadBinary allows to read remote item in chunks using stream-like API. - // The difference to StreamBinary is that the client receives single response for each - // call and has to send new calls to the server to get fresh data from remote item. - // Each response (each chunk from server) keeps not more than 3.9MiB of data. + // ReadBinary allows reading a remote item in chunks using a stream-like API. + // The client receives a single response for each call and has to send new calls + // to the server to get fresh data from the remote item. + // Each response (each chunk from the server) contains no more than 3.9MiB of data. rpc ReadBinary(StreamingAPI.ReadBinary) returns (StreamingAPI.Response) { option (google.api.http) = { post: "/v1/read/binary" @@ -32,19 +22,10 @@ service Streaming { }; } - // StreamText provides stream of textual file, splitting the data by newline symbol. - // Each response message keeps one single line of text from data source. - rpc StreamText(StreamingAPI.StreamText) returns (stream StreamingAPI.Response) { - option (google.api.http) = { - post: "/v1/stream/text" - body: "*" - }; - } - - // ReadBinary allows to read remote item in chunks using stream-like API. - // The difference to StreamBinary is that the client receives single response for each - // call and has to send new calls to the server to get fresh data from remote item. - // Each response (each chunk from server) keeps not more than 3.9MiB of data. + // ReadText allows reading a remote item in chunks using a stream-like API. + // The client receives a single response for each call and has to send new calls + // to the server to get fresh data from the remote item. + // Each response (each chunk from the server) contains no more than 3.9MiB of data. rpc ReadText(StreamingAPI.ReadText) returns (StreamingAPI.Response) { option (google.api.http) = { post: "/v1/read/text" @@ -52,14 +33,14 @@ service Streaming { }; } - // LastLines provides single message with the last lines from data source. - // When search pattern is specified, the last lines matching the given pattern are returned. - // The lines are returned in reversed order, as server reads data source from the end. - // Consider it as equivalent to 'tac | grep | head -n ' - // The returned in the response points to the _beginning_ of the last - // line found, so client can continue reading the file backwards in subsequent calls. - // This means, that use of this in ReadText() will return you the same line - // returned last in LastLines() data. + // LastLines provides a single message with the last lines from the data source. + // When a search pattern is specified, the last lines matching the given pattern are returned. + // The lines are returned in reverse order, as the server reads the data source from the end. + // Consider it equivalent to 'tac | grep | head -n ' + // The returned in the response points to the _beginning_ of the last + // line found, so the client can continue reading the file backwards in subsequent calls. + // This means that the use of this in ReadText() will return the last line + // returned in the LastLines() response. rpc LastLines(StreamingAPI.LastLines) returns (StreamingAPI.Response) { option (google.api.http) = { post: "/v1/last-lines" @@ -69,33 +50,13 @@ service Streaming { } message StreamingAPI { - message StreamBinary { - // of Stream resource, that keeps info on item to be streamed. - uint64 resource_id = 1; - - // makes streamer to perform seek operation to given offset before sending the data. - int64 offset = 2; - - // limits the maximum size of for each response message in stream. - // - // Default value: 32 768 (32 KiB) - // Max value: 3900 * 1024 (3.9 MiB) - optional uint32 chunk_size = 11; - - // allows client to limit total data sent from server. - // This limit is aggregation of all data, sent in all chunks. - // E.g. to read 2000 bytes of data in chunks of at most - // 130 bytes, use = 130; = 2000. - // For storage item of appropriate size this settings will result in - // 16 messages from server: 15 of 130 bytes and one of 50 bytes. - optional int64 read_limit = 20; - } - message ReadBinary { - // of Stream resource, that keeps info on item to be streamed. + // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 3; - // makes streamer to perform seek operation to given offset before sending the data. + // makes the streamer perform a seek operation to the given offset before sending the data. int64 offset = 2; // limits the maximum size of for response message in stream. @@ -105,104 +66,80 @@ message StreamingAPI { optional uint32 chunk_size = 11; } - message StreamText { - // of Stream resource, that keeps info on item to be streamed. - uint64 resource_id = 1; - - // makes streamer to perform seek operation to given offset before sending the contents. - // This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - // Client can just use the value of the last response from server to continue streaming after reconnection. - int64 offset = 2; - - // allows client to limit total data sent from server. - // This limit is aggregation of all data, sent in all chunks, measured - // in lines of text. - // E.g. to read top 1000 lines from stream source, use = 1000. - // When both and / are set, the is applied first. - // this is equivalent to 'head -n | grep '. - optional int64 read_limit = 20; - - // is substring for line search pattern. - // This option makes controller to send to the client only lines, that - // have given substring. - optional string search = 21; - - // is regular expression for line search pattern. - // This option makes controller to send to the client only lines, that - // match given regular expression. - optional string search_re = 22; - } - message ReadText { - // of Stream resource, that keeps info on item to be streamed. + // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 3; - // makes streamer to perform seek operation to given offset before sending the contents. - // This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - // Client can just use the value of the last response from server to continue streaming after reconnection. + // makes the streamer perform a seek operation to the given offset before sending the contents. + // This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + // The client can just use the value of the last response from the server to continue streaming after reconnection. int64 offset = 2; - // allows client to limit total data sent from server. + // allows the client to limit total data sent from the server. // Measured in lines of text. // E.g. to read top 1000 lines from stream source, use = 1000. // When both and / are set, the is applied first. - // this is equivalent to 'head -n | grep '. - // At most 3.9 MiB (3900 * 1024 KiB) of data is returned in single read regardless of option + // This is equivalent to 'head -n | grep '. + // At most 3.9 MiB (3900 KiB) of data is returned in a single read regardless of the option // Only full lines of text are returned except for the last line from the completed source - // (the one that is not expected to have new data, like blob in storage) + // (the one that is not expected to have new data, like a blob in storage) optional int64 read_limit = 20; - // is substring for line search pattern. - // This option makes controller to send to the client only lines, that - // have given substring. + // is a substring for the line search pattern. + // This option makes the controller send to the client only lines that + // have the given substring. optional string search = 21; - // is regular expression for line search pattern. - // This option makes controller to send to the client only lines, that - // match given regular expression. + // is a regular expression for the line search pattern. + // This option makes the controller send to the client only lines that + // match the given regular expression. optional string search_re = 22; } message LastLines { - // of Stream resource, that keeps info on item to be streamed. + // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 4; - // makes streamer to perform seek operation to given offset before sending the contents. - // This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - // By default, LastLines starts to treat the data source from the very last byte available in data stream - // at the moment of call, but client can set the server to start from earlier position. + // makes the streamer perform a seek operation to the given offset before sending the contents. + // This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + // By default, LastLines starts reading the data source from the very last byte available in the data stream + // at the moment of the call, but the client can set the server to start from an earlier position. optional int64 offset = 2; - // makes streamer to return up to lines to the client. + // makes the streamer return up to lines to the client. // Default value: 1 optional int32 line_count = 3; - // is substring for line search pattern. - // This option makes controller to send to the client only lines, that - // have given substring. + // is a substring for the line search pattern. + // This option makes the controller send to the client only lines that + // have the given substring. optional string search = 21; - // is regular expression for line search pattern. - // This option makes controller to send to the client only lines, that - // match given regular expression. + // is a regular expression for the line search pattern. + // This option makes the controller send to the client only lines that + // match the given regular expression. optional string search_re = 22; } message Response { - // data chunk from item, starting from the of the previous message in the same stream. + // data chunk from the item, starting from the of the previous message in the same stream. bytes data = 1; // is the actual size of the streamed item at the moment of this message. - // This might be not a final amount of streamed data, as stream source can be updated - // by other independent process (e.g., data is written to log file). - // This field in combination with shows, how far the client is from the end + // This might not be the final amount of streamed data, as the stream source can be updated + // by another independent process (e.g., data is written to a log file). + // This field in combination with shows how far the client is from the end // of the data right now. uint64 size = 2; // is the new offset in bytes from the start of the streamed item, - // including size of in current response. - // Call to Stream rpc with = will continue - // streaming from the place of last received message + // including the size of in the current response. + // A call to the Stream RPC with = will continue + // streaming from the place of the last received message // (e.g. = - 1 will repeat the last byte of // previously received ) uint64 new_offset = 3; diff --git a/lib/node/pl-drivers/proto/shared/uploadapi/openapi.yaml b/lib/node/pl-drivers/proto/shared/uploadapi/openapi.yaml index e4502f5d66..b5ee0df28f 100644 --- a/lib/node/pl-drivers/proto/shared/uploadapi/openapi.yaml +++ b/lib/node/pl-drivers/proto/shared/uploadapi/openapi.yaml @@ -3,273 +3,347 @@ openapi: 3.0.3 info: - title: Upload API - description: Upload provides access to data upload feature, allowing clients to uplad data to Platforma. - version: 0.0.1 + title: Upload API + description: Upload provides access to the data upload feature, allowing clients to upload data to Platforma. + version: 0.0.1 paths: - /v1/upload/finalize: - post: - tags: - - Upload - description: |- - Finalize informs Controller that the upload process is done. - Returns an error, if the total size of all uploaded chunks is not equal to - size of the upload given in Init. - operationId: Upload_Finalize - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Finalize_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Finalize_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/upload/get-part-url: - post: - tags: - - Upload - description: |- - GetPartURL provides URL for uploading chunk of the data. - Clients are expected to put their data directly to the given location. - operationId: Upload_GetPartURL - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/GetPartURL_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/GetPartURL_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/upload/init: - post: - tags: - - Upload - description: Init upload, making controller to do all required preparation steps. - operationId: Upload_Init - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/Init_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/Init_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" - /v1/upload/update-progress: - post: - tags: - - Upload - description: UpdateProgress of the upload, so other clients can see how it is going. - operationId: Upload_UpdateProgress - requestBody: - content: - application/json: - schema: - $ref: "#/components/schemas/UpdateProgress_Request" - required: true - responses: - "200": - description: OK - content: - application/json: - schema: - $ref: "#/components/schemas/UpdateProgress_Response" - default: - description: Default error response - content: - application/json: - schema: - $ref: "#/components/schemas/Status" + /v1/upload/finalize: + post: + tags: + - Upload + description: |- + Finalize informs the Controller that the upload process is done. + Returns an error if the total size of all uploaded chunks is not equal to + the size of the upload given in Init. + operationId: Upload_Finalize + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Finalize_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Finalize_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/upload/get-part-url: + post: + tags: + - Upload + description: |- + GetPartURL provides a URL for uploading a chunk of the data. + Clients are expected to put their data directly to the given location. + operationId: Upload_GetPartURL + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/GetPartURL_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/GetPartURL_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/upload/init: + post: + tags: + - Upload + description: Initialize the upload, making the controller do all required preparation steps. + operationId: Upload_Init + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Init_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Init_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/upload/reset: + post: + tags: + - Upload + description: |- + Reset the current upload progress (remove uploaded parts and so on) to start over. + This is useful when a checksum mismatch is detected by the client during upload + and the data requires a full re-upload. + The server never resets the upload on its own; it is always the client's initiative. + operationId: Upload_Reset + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Reset_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/Reset_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' + /v1/upload/update-progress: + post: + tags: + - Upload + description: Update the progress of the upload, so other clients can see how it is going. + operationId: Upload_UpdateProgress + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProgress_Request' + required: true + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: '#/components/schemas/UpdateProgress_Response' + default: + description: Default error response + content: + application/json: + schema: + $ref: '#/components/schemas/Status' components: - schemas: - Finalize_Request: - type: object - properties: - resourceId: - type: string - Finalize_Response: - type: object - properties: {} - GetPartURL_HTTPHeader: - type: object - properties: - name: - type: string - value: - type: string - GetPartURL_Request: - type: object - properties: - resourceId: - type: string - description: Id of upload resource - partNumber: - type: string - description: |- - Part to be uploaded. It is responsibility of the Client to watch after already uploaded parts: - - client can request an URL for the same part twice (request -> request) without errors; - - client can request an URL for alrady uploaded part (request -> upload -> request) without errors. + schemas: + Finalize_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + checksumAlgorithm: + type: integer + description: |- + The client can send the final checksum of the whole file to verify the upload. + The algorithm used to calculate this final checksum must match the algorithm from + Init.Response. The server may reject the request on a mismatch + (meaning the server does not support the given algorithm). + format: enum + checksum: + type: string + description: |- + Checksum of the whole file for validation. When provided, the server may verify the upload before + marking it final. This is optional behavior that depends on the particular storage driver + used on the backend side for this upload. See the storage driver's implementation for more details. + format: bytes + Finalize_Response: + type: object + properties: {} + GetPartURL_HTTPHeader: + type: object + properties: + name: + type: string + value: + type: string + GetPartURL_Request: + type: object + properties: + resourceId: + type: string + description: ID of the upload resource + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + partNumber: + type: string + description: |- + Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: + - client can request a URL for the same part twice (request -> request) without errors; + - client can request a URL for an already uploaded part (request -> upload -> request) without errors. - Parts enumeration starts from 1. - uploadedPartSize: - type: string - description: |- - Size of the part uploaded by client earlier. Allows controller to count upload progress - based on client's input. - Client is free to never sent this value (send zeroes in each request). - isInternalUse: - type: boolean - description: |- - Do we need to presign URL for internal use. - Controllers could use this if they are trying to download something from internal network. - For backward compatibility, by default pl backend will presign external urls. - partChecksum: - type: string - description: |- - Checksum is not used for now, but it is here for case - where signing checksum header is required. - GetPartURL_Response: - type: object - properties: - uploadUrl: - type: string - description: URL for chunk upload - method: - type: string - description: HTTP method to use for chunk upload, say 'PUT' or 'POST'. - headers: - type: array - items: - $ref: "#/components/schemas/GetPartURL_HTTPHeader" - description: |- - List of headers with their values, MANDATORY to be sent by the client for the upload. - The destination service (the one, that will handle upload request for specific part) - may reject the request if it would not keep the given headers. - chunkStart: - type: string - description: |- - The number of the _first_ byte in the chunk. - Absolute position from the start of the file ( file.seek(, SEEK_START) ). - The client is expected to send [; ) range. - chunkEnd: - type: string - description: |- - The number of the byte _after_ the last to be sent in the chunk. - Absolute position from the start of the file. - The client is expected to send [; ) range. - GoogleProtobufAny: - type: object - properties: - "@type": - type: string - description: The type of the serialized message. - additionalProperties: true - description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. - Init_Request: - type: object - properties: - resourceId: - type: string - description: Id of upload resource - Init_Response: - type: object - properties: - partsCount: - type: string - description: |- - Number of parts in this upload. - For parallel upload support, client can generate any number of part upload URLs - at the moment and upload them in parallel. - keeps the number of chunks supported by this upload. - The parts count is calculated from the planned size of the upload, controller - configuration and underlying storage restrictions. - partSize: - type: string - checksumAlgorithm: - type: integer - description: Checksum algorithm to use for the part upload. - format: enum - checksumHeader: - type: string - description: Header name to use for the checksum. - uploadedParts: - type: array - items: - type: string - description: |- - List of IDs of parts that were already uploaded by client. - Helps client to recover upload and skip already done parts - after being interrupted in the middle of the upload - (say, because of the restart). - Parts enumeration starts from 1. - Status: - type: object - properties: - code: - type: integer - description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - format: int32 - message: - type: string - description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. - details: - type: array - items: - $ref: "#/components/schemas/GoogleProtobufAny" - description: A list of messages that carry the error details. There is a common set of message types for APIs to use. - description: "The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors)." - UpdateProgress_Request: - type: object - properties: - resourceId: - type: string - description: Id of upload resource - bytesProcessed: - type: string - description: |- - Amount of bytes, uploaded since the earlier call to UpdateProgress. - This value is just blindly added to the 'bytes_processed' of progress report, - so other clients can see the upload progress. - If client uploads the data in several streams (several chunks in parallel), it - can safely send progress updates individually for each of the streams, just counting - bytes uploaded by particular stream. + Parts enumeration starts from 1. + uploadedPartSize: + type: string + description: |- + Size of the part uploaded by the client earlier. Allows the controller to count upload progress + based on the client's input. + The client is free to never send this value (send zeroes in each request). + isInternalUse: + type: boolean + description: |- + Whether to presign the URL for internal use. + Controllers could use this if they are trying to upload something from the internal network. + For backward compatibility, by default the pl backend will presign external URLs. + partChecksum: + type: string + description: |- + Checksum is not used for now, but it is here for the case + where signing the checksum header is required. + GetPartURL_Response: + type: object + properties: + uploadUrl: + type: string + description: URL for chunk upload + method: + type: string + description: HTTP method to use for chunk upload, say 'PUT' or 'POST'. + headers: + type: array + items: + $ref: '#/components/schemas/GetPartURL_HTTPHeader' + description: |- + List of headers with their values that MUST be sent by the client for the upload. + The destination service (the one that will handle the upload request for a specific part) + may reject the request if it does not include the given headers. + chunkStart: + type: string + description: |- + The number of the _first_ byte in the chunk. + Absolute position from the start of the file ( file.seek(, SEEK_START) ). + The client is expected to send the [; ) range. + chunkEnd: + type: string + description: |- + The number of the byte _after_ the last to be sent in the chunk. + Absolute position from the start of the file. + The client is expected to send the [; ) range. + GoogleProtobufAny: + type: object + properties: + '@type': + type: string + description: The type of the serialized message. + additionalProperties: true + description: Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. + Init_Request: + type: object + properties: + resourceId: + type: string + description: ID of the upload resource + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + Init_Response: + type: object + properties: + partsCount: + type: string + description: |- + Number of parts in this upload. + For parallel upload support, the client can generate any number of part upload URLs + at once and upload them in parallel. + keeps the number of chunks supported by this upload. + The parts count is calculated from the planned size of the upload, controller + configuration, and underlying storage restrictions. + partSize: + type: string + checksumAlgorithm: + type: integer + description: Checksum algorithm to use for each part upload and final verification. + format: enum + checksumHeader: + type: string + description: |- + Header name to use for the checksum in each part upload request. + A non-empty value combined with checksum_algorithm enables each part upload verification. + uploadedParts: + type: array + items: + type: string + description: |- + List of IDs of parts that were already uploaded by the client. + Helps the client recover the upload and skip already uploaded parts + after being interrupted in the middle of the upload + (say, because of a restart). + Parts enumeration starts from 1. + Reset_Request: + type: object + properties: + resourceId: + type: string + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + Reset_Response: + type: object + properties: {} + Status: + type: object + properties: + code: + type: integer + description: The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + format: int32 + message: + type: string + description: A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. + details: + type: array + items: + $ref: '#/components/schemas/GoogleProtobufAny' + description: A list of messages that carry the error details. There is a common set of message types for APIs to use. + description: 'The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors).' + UpdateProgress_Request: + type: object + properties: + resourceId: + type: string + description: ID of the upload resource + resourceSignature: + type: string + description: Signature proving the caller is authorized to access this resource. + format: bytes + bytesProcessed: + type: string + description: |- + Number of bytes uploaded since the earlier call to UpdateProgress. + This value is just blindly added to the 'bytes_processed' of the progress report, + so other clients can see the upload progress. + If the client uploads the data in several streams (several chunks in parallel), it + can safely send progress updates individually for each of the streams, just counting + bytes uploaded by a particular stream. - Negative value can be used to report about upload retry: when upload was interrupted, - part of the uploaded data is lost and require re-upload. - UpdateProgress_Response: - type: object - properties: {} + A negative value can be used to report an upload retry: when the upload was interrupted, + part of the uploaded data is lost and requires a re-upload. + UpdateProgress_Response: + type: object + properties: {} tags: - - name: Upload + - name: Upload diff --git a/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto b/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto index 69c4ef6856..b2fe9ab5ff 100644 --- a/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto @@ -7,11 +7,11 @@ option go_package = "github.com/milaboratory/pl/controllers/shared/grpc/uploadap import "github.com/googleapis/googleapis/google/api/annotations.proto"; // -// Upload provides access to data upload feature, allowing clients to uplad data to Platforma. +// Upload provides access to the data upload feature, allowing clients to upload data to Platforma. // service Upload { // - // Init upload, making controller to do all required preparation steps. + // Initialize the upload, making the controller do all required preparation steps. // rpc Init(UploadAPI.Init.Request) returns (UploadAPI.Init.Response) { option (google.api.http) = { @@ -21,7 +21,7 @@ service Upload { } // - // GetPartURL provides URL for uploading chunk of the data. + // GetPartURL provides a URL for uploading a chunk of the data. // Clients are expected to put their data directly to the given location. // rpc GetPartURL(UploadAPI.GetPartURL.Request) returns (UploadAPI.GetPartURL.Response) { @@ -32,7 +32,7 @@ service Upload { } // - // UpdateProgress of the upload, so other clients can see how it is going. + // Update the progress of the upload, so other clients can see how it is going. // rpc UpdateProgress(UploadAPI.UpdateProgress.Request) returns (UploadAPI.UpdateProgress.Response) { option (google.api.http) = { @@ -42,9 +42,9 @@ service Upload { } // - // Finalize informs Controller that the upload process is done. - // Returns an error, if the total size of all uploaded chunks is not equal to - // size of the upload given in Init. + // Finalize informs the Controller that the upload process is done. + // Returns an error if the total size of all uploaded chunks is not equal to + // the size of the upload given in Init. // rpc Finalize(UploadAPI.Finalize.Request) returns (UploadAPI.Finalize.Response) { option (google.api.http) = { @@ -52,6 +52,19 @@ service Upload { body: "*" }; } + + // + // Reset the current upload progress (remove uploaded parts and so on) to start over. + // This is useful when a checksum mismatch is detected by the client during upload + // and the data requires a full re-upload. + // The server never resets the upload on its own; it is always the client's initiative. + // + rpc Reset(UploadAPI.Reset.Request) returns (UploadAPI.Reset.Response) { + option (google.api.http) = { + post: "/v1/upload/reset" + body: "*" + }; + } } message UploadAPI { @@ -62,30 +75,33 @@ message UploadAPI { message Init { message Request { - // Id of upload resource + // ID of the upload resource uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 2; } message Response { // Number of parts in this upload. - // For parallel upload support, client can generate any number of part upload URLs - // at the moment and upload them in parallel. + // For parallel upload support, the client can generate any number of part upload URLs + // at once and upload them in parallel. // keeps the number of chunks supported by this upload. // The parts count is calculated from the planned size of the upload, controller - // configuration and underlying storage restrictions. + // configuration, and underlying storage restrictions. uint64 parts_count = 1; uint64 part_size = 3; - // Checksum algorithm to use for the part upload. + // Checksum algorithm to use for each part upload and final verification. ChecksumAlgorithm checksum_algorithm = 4; - // Header name to use for the checksum. + // Header name to use for the checksum in each part upload request. + // A non-empty value combined with checksum_algorithm enables each part upload verification. string checksum_header = 5; - // List of IDs of parts that were already uploaded by client. - // Helps client to recover upload and skip already done parts + // List of IDs of parts that were already uploaded by the client. + // Helps the client recover the upload and skip already uploaded parts // after being interrupted in the middle of the upload - // (say, because of the restart). + // (say, because of a restart). // Parts enumeration starts from 1. repeated uint64 uploaded_parts = 2; } @@ -93,18 +109,20 @@ message UploadAPI { message UpdateProgress { message Request { - // Id of upload resource + // ID of the upload resource uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 3; - // Amount of bytes, uploaded since the earlier call to UpdateProgress. - // This value is just blindly added to the 'bytes_processed' of progress report, + // Number of bytes uploaded since the earlier call to UpdateProgress. + // This value is just blindly added to the 'bytes_processed' of the progress report, // so other clients can see the upload progress. - // If client uploads the data in several streams (several chunks in parallel), it + // If the client uploads the data in several streams (several chunks in parallel), it // can safely send progress updates individually for each of the streams, just counting - // bytes uploaded by particular stream. + // bytes uploaded by a particular stream. // - // Negative value can be used to report about upload retry: when upload was interrupted, - // part of the uploaded data is lost and require re-upload. + // A negative value can be used to report an upload retry: when the upload was interrupted, + // part of the uploaded data is lost and requires a re-upload. int64 bytes_processed = 2; } @@ -113,28 +131,30 @@ message UploadAPI { message GetPartURL { message Request { - // Id of upload resource + // ID of the upload resource uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 6; - // Part to be uploaded. It is responsibility of the Client to watch after already uploaded parts: - // - client can request an URL for the same part twice (request -> request) without errors; - // - client can request an URL for alrady uploaded part (request -> upload -> request) without errors. + // Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: + // - client can request a URL for the same part twice (request -> request) without errors; + // - client can request a URL for an already uploaded part (request -> upload -> request) without errors. // // Parts enumeration starts from 1. uint64 part_number = 2; - // Size of the part uploaded by client earlier. Allows controller to count upload progress - // based on client's input. - // Client is free to never sent this value (send zeroes in each request). + // Size of the part uploaded by the client earlier. Allows the controller to count upload progress + // based on the client's input. + // The client is free to never send this value (send zeroes in each request). uint64 uploaded_part_size = 3; - // Do we need to presign URL for internal use. - // Controllers could use this if they are trying to download something from internal network. - // For backward compatibility, by default pl backend will presign external urls. + // Whether to presign the URL for internal use. + // Controllers could use this if they are trying to upload something from the internal network. + // For backward compatibility, by default the pl backend will presign external URLs. bool is_internal_use = 4; - // Checksum is not used for now, but it is here for case - // where signing checksum header is required. + // Checksum is not used for now, but it is here for the case + // where signing the checksum header is required. string part_checksum = 5; } @@ -150,19 +170,19 @@ message UploadAPI { // HTTP method to use for chunk upload, say 'PUT' or 'POST'. string method = 2; - // List of headers with their values, MANDATORY to be sent by the client for the upload. - // The destination service (the one, that will handle upload request for specific part) - // may reject the request if it would not keep the given headers. + // List of headers with their values that MUST be sent by the client for the upload. + // The destination service (the one that will handle the upload request for a specific part) + // may reject the request if it does not include the given headers. repeated HTTPHeader headers = 3; // The number of the _first_ byte in the chunk. // Absolute position from the start of the file ( file.seek(, SEEK_START) ). - // The client is expected to send [; ) range. + // The client is expected to send the [; ) range. uint64 chunk_start = 4; // The number of the byte _after_ the last to be sent in the chunk. // Absolute position from the start of the file. - // The client is expected to send [; ) range. + // The client is expected to send the [; ) range. uint64 chunk_end = 5; } } @@ -170,8 +190,31 @@ message UploadAPI { message Finalize { message Request { uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 4; + + // The client can send the final checksum of the whole file to verify the upload. + // The algorithm used to calculate this final checksum must match the algorithm from + // Init.Response. The server may reject the request on a mismatch + // (meaning the server does not support the given algorithm). + ChecksumAlgorithm checksum_algorithm = 2; + + // Checksum of the whole file for validation. When provided, the server may verify the upload before + // marking it final. This is optional behavior that depends on the particular storage driver + // used on the backend side for this upload. See the storage driver's implementation for more details. + bytes checksum = 3; } message Response {} } + + message Reset { + message Request { + uint64 resource_id = 1; + // Signature proving the caller is authorized to access this resource. + bytes resource_signature = 2; + } + + message Response {} + } } diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts index 657379a688..2eb03c4bb1 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.client.ts @@ -11,7 +11,7 @@ import type { UnaryCall } from "@protobuf-ts/runtime-rpc"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; /** * - * Download provides access to any data, that can be downloaded via network. + * Download provides access to any data that can be downloaded over the network. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Download @@ -24,7 +24,7 @@ export interface IDownloadClient { } /** * - * Download provides access to any data, that can be downloaded via network. + * Download provides access to any data that can be downloaded over the network. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Download diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts index b9a08258ec..cc22ee7077 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts @@ -30,9 +30,15 @@ export interface DownloadAPI_GetDownloadURL_Request { */ resourceId: bigint; /** - * Pass `true` here if the blob will be downloaded from internal network, - * e.g. controllers could use this if they are trying to download something from internal network. - * For backward compatibility, by default pl treats all requests as from external network. + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; + /** + * Pass `true` here if the blob will be downloaded from the internal network, + * e.g. controllers could use this if they are trying to download something from the internal network. + * For backward compatibility, by default pl treats all requests as from the external network. * * @generated from protobuf field: bool is_internal_use = 2 */ @@ -43,11 +49,11 @@ export interface DownloadAPI_GetDownloadURL_Request { */ export interface DownloadAPI_GetDownloadURL_HTTPHeader { /** - * @generated from protobuf field: string Name = 1 + * @generated from protobuf field: string name = 1 */ name: string; /** - * @generated from protobuf field: string Value = 2 + * @generated from protobuf field: string value = 2 */ value: string; } @@ -145,12 +151,14 @@ class DownloadAPI_GetDownloadURL_Request$Type extends MessageType): DownloadAPI_GetDownloadURL_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.isInternalUse = false; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -164,6 +172,9 @@ class DownloadAPI_GetDownloadURL_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.DownloadAPI.GetDownloadURL.HTTPHeader", [ - { no: 1, name: "Name", kind: "scalar", jsonName: "Name", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "Value", kind: "scalar", jsonName: "Value", T: 9 /*ScalarType.STRING*/ } + { no: 1, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): DownloadAPI_GetDownloadURL_HTTPHeader { @@ -216,10 +230,10 @@ class DownloadAPI_GetDownloadURL_HTTPHeader$Type extends MessageType + + * full_name is the full name of the item, relative to the storage root. + * It is + . * The , used in names, is storage-specific and is NOT guaranteed to be '/'. * * @generated from protobuf field: string full_name = 10 */ fullName: string; /** - * directory, the item is located in. The value here is always a prefix of name: + * The directory the item is located in. The value here is always a prefix of name: * name.HasPrefix(directory) is always true. * * @generated from protobuf field: string directory = 11 */ directory: string; /** - * last_modified keeps the item last modification timestamp + * last_modified keeps the item's last modification timestamp. * * @generated from protobuf field: google.protobuf.Timestamp last_modified = 12 */ lastModified?: Timestamp; /** - * version of item in storage. - * When storage supports versioning or provides checksums for the data stored, + * Version of item in storage. + * When the storage supports versioning or provides checksums for the data stored, * the field keeps that data. - * If not - it keeps the any simple combination of item attributes, that helps to - * detect if the contents of item has changed, e.g. +. - * Anyway, client should not try to interpret this field, but should provide it to the Platform - * in operations with given item (like BlobImportInternal) to help Platform with deduplication. + * If not, it keeps any simple combination of item attributes that helps to + * detect if the contents of the item have changed, e.g. +. + * Anyway, the client should not try to interpret this field, but should provide it to the Platform + * in operations with a given item (like BlobImportInternal) to help the Platform with deduplication. * * @generated from protobuf field: string version = 13 */ @@ -90,8 +90,14 @@ export interface LsAPI_List_Request { */ resourceId: bigint; /** - * location to list, absolute to storage root. Only items, that have starting - * from are included into list response. + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; + /** + * Location to list, relative to the storage root. Only items that have starting + * with are included in the list response. * * @generated from protobuf field: string location = 2 */ @@ -102,7 +108,7 @@ export interface LsAPI_List_Request { */ export interface LsAPI_List_Response { /** - * List of the full (absolute to storage root) names of items from storage. + * List of the full (relative to storage root) names of items from storage. * E.g., for 'fs' storage each name will consist of names of all directories, where the * item is located, and the item name itself. * The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. @@ -111,7 +117,7 @@ export interface LsAPI_List_Response { */ items: LsAPI_ListItem[]; /** - * delimiter is path separator, used in this storage. Client can use it to parse item names into parts, + * The delimiter is the path separator used in this storage. The client can use it to parse item names into parts, * to extract directory names. * * @generated from protobuf field: string delimiter = 2 @@ -293,12 +299,14 @@ class LsAPI_List_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.LsAPI.List.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "location", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): LsAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.location = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -312,6 +320,9 @@ class LsAPI_List_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 3: + message.resourceSignature = reader.bytes(); + break; case /* string location */ 2: message.location = reader.string(); break; @@ -333,6 +344,9 @@ class LsAPI_List_Request$Type extends MessageType { /* string location = 2; */ if (message.location !== "") writer.tag(2, WireType.LengthDelimited).string(message.location); + /* bytes resource_signature = 3; */ + if (message.resourceSignature.length) + writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts index ac82b3f73c..efa75054de 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.client.ts @@ -14,7 +14,7 @@ import type { UnaryCall } from "@protobuf-ts/runtime-rpc"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; /** * - * Progress provides access to progress of any long-running process associated with resource. + * Progress provides access to the progress of any long-running process associated with a resource. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Progress @@ -31,7 +31,7 @@ export interface IProgressClient { } /** * - * Progress provides access to progress of any long-running process associated with resource. + * Progress provides access to the progress of any long-running process associated with a resource. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Progress diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts index 95d1dc193c..1a37f63dce 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts @@ -57,6 +57,10 @@ export interface ProgressAPI_GetStatus_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.ProgressAPI.GetStatus.Response @@ -80,6 +84,10 @@ export interface ProgressAPI_RealtimeStatus_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; /** * @generated from protobuf field: google.protobuf.Duration update_interval = 2 */ @@ -253,12 +261,14 @@ export const ProgressAPI_GetStatus = new ProgressAPI_GetStatus$Type(); class ProgressAPI_GetStatus_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.ProgressAPI.GetStatus.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ProgressAPI_GetStatus_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -271,6 +281,9 @@ class ProgressAPI_GetStatus_Request$Type extends MessageType Duration } ]); } create(value?: PartialMessage): ProgressAPI_RealtimeStatus_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -403,6 +421,9 @@ class ProgressAPI_RealtimeStatus_Request$Type extends MessageType; - /** - * ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. + * ReadBinary allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. * * @generated from protobuf rpc: ReadBinary */ readBinary(input: StreamingAPI_ReadBinary, options?: RpcOptions): UnaryCall; /** - * StreamText provides stream of textual file, splitting the data by newline symbol. - * Each response message keeps one single line of text from data source. - * - * @generated from protobuf rpc: StreamText - */ - streamText(input: StreamingAPI_StreamText, options?: RpcOptions): ServerStreamingCall; - /** - * ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. + * ReadText allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. * * @generated from protobuf rpc: ReadText */ readText(input: StreamingAPI_ReadText, options?: RpcOptions): UnaryCall; /** - * LastLines provides single message with the last lines from data source. - * When search pattern is specified, the last lines matching the given pattern are returned. - * The lines are returned in reversed order, as server reads data source from the end. - * Consider it as equivalent to 'tac | grep | head -n ' + * LastLines provides a single message with the last lines from the data source. + * When a search pattern is specified, the last lines matching the given pattern are returned. + * The lines are returned in reverse order, as the server reads the data source from the end. + * Consider it equivalent to 'tac | grep | head -n ' * The returned in the response points to the _beginning_ of the last - * line found, so client can continue reading the file backwards in subsequent calls. - * This means, that use of this in ReadText() will return you the same line - * returned last in LastLines() data. + * line found, so the client can continue reading the file backwards in subsequent calls. + * This means that the use of this in ReadText() will return the last line + * returned in the LastLines() response. * * @generated from protobuf rpc: LastLines */ @@ -72,8 +54,8 @@ export interface IStreamingClient { } /** * - * Streaming provides access to online data stream from item in storage. Whenever item is appended with data, - * the caller receives this fresh data in stream from server. + * Streaming provides access to an online data stream from an item in storage. Whenever data is appended to an item, + * the caller receives this fresh data in a stream from the server. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Streaming @@ -85,64 +67,43 @@ export class StreamingClient implements IStreamingClient, ServiceInfo { constructor(private readonly _transport: RpcTransport) { } /** - * StreamBinary provides stream of binary file. Each response message keeps - * one single chunk of binary data from data source. See StreamingAPI.Binary message - * for more info on available options. - * - * @generated from protobuf rpc: StreamBinary - */ - streamBinary(input: StreamingAPI_StreamBinary, options?: RpcOptions): ServerStreamingCall { - const method = this.methods[0], opt = this._transport.mergeOptions(options); - return stackIntercept("serverStreaming", this._transport, method, opt, input); - } - /** - * ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. + * ReadBinary allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. * * @generated from protobuf rpc: ReadBinary */ readBinary(input: StreamingAPI_ReadBinary, options?: RpcOptions): UnaryCall { - const method = this.methods[1], opt = this._transport.mergeOptions(options); + const method = this.methods[0], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** - * StreamText provides stream of textual file, splitting the data by newline symbol. - * Each response message keeps one single line of text from data source. - * - * @generated from protobuf rpc: StreamText - */ - streamText(input: StreamingAPI_StreamText, options?: RpcOptions): ServerStreamingCall { - const method = this.methods[2], opt = this._transport.mergeOptions(options); - return stackIntercept("serverStreaming", this._transport, method, opt, input); - } - /** - * ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. + * ReadText allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. * * @generated from protobuf rpc: ReadText */ readText(input: StreamingAPI_ReadText, options?: RpcOptions): UnaryCall { - const method = this.methods[3], opt = this._transport.mergeOptions(options); + const method = this.methods[1], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } /** - * LastLines provides single message with the last lines from data source. - * When search pattern is specified, the last lines matching the given pattern are returned. - * The lines are returned in reversed order, as server reads data source from the end. - * Consider it as equivalent to 'tac | grep | head -n ' + * LastLines provides a single message with the last lines from the data source. + * When a search pattern is specified, the last lines matching the given pattern are returned. + * The lines are returned in reverse order, as the server reads the data source from the end. + * Consider it equivalent to 'tac | grep | head -n ' * The returned in the response points to the _beginning_ of the last - * line found, so client can continue reading the file backwards in subsequent calls. - * This means, that use of this in ReadText() will return you the same line - * returned last in LastLines() data. + * line found, so the client can continue reading the file backwards in subsequent calls. + * This means that the use of this in ReadText() will return the last line + * returned in the LastLines() response. * * @generated from protobuf rpc: LastLines */ lastLines(input: StreamingAPI_LastLines, options?: RpcOptions): UnaryCall { - const method = this.methods[4], opt = this._transport.mergeOptions(options); + const method = this.methods[2], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } } diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts index 0506341a75..cfa723cb8c 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.ts @@ -17,54 +17,23 @@ import { MessageType } from "@protobuf-ts/runtime"; export interface StreamingAPI { } /** - * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.StreamBinary + * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.ReadBinary */ -export interface StreamingAPI_StreamBinary { +export interface StreamingAPI_ReadBinary { /** - * of Stream resource, that keeps info on item to be streamed. + * of Stream resource that keeps info on item to be streamed. * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; /** - * makes streamer to perform seek operation to given offset before sending the data. - * - * @generated from protobuf field: int64 offset = 2 - */ - offset: bigint; - /** - * limits the maximum size of for each response message in stream. - * - * Default value: 32 768 (32 KiB) - * Max value: 3900 * 1024 (3.9 MiB) - * - * @generated from protobuf field: optional uint32 chunk_size = 11 - */ - chunkSize?: number; - /** - * allows client to limit total data sent from server. - * This limit is aggregation of all data, sent in all chunks. - * E.g. to read 2000 bytes of data in chunks of at most - * 130 bytes, use = 130; = 2000. - * For storage item of appropriate size this settings will result in - * 16 messages from server: 15 of 130 bytes and one of 50 bytes. - * - * @generated from protobuf field: optional int64 read_limit = 20 - */ - readLimit?: bigint; -} -/** - * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.ReadBinary - */ -export interface StreamingAPI_ReadBinary { - /** - * of Stream resource, that keeps info on item to be streamed. + * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: uint64 resource_id = 1 + * @generated from protobuf field: bytes resource_signature = 3 */ - resourceId: bigint; + resourceSignature: Uint8Array; /** - * makes streamer to perform seek operation to given offset before sending the data. + * makes the streamer perform a seek operation to the given offset before sending the data. * * @generated from protobuf field: int64 offset = 2 */ @@ -80,94 +49,54 @@ export interface StreamingAPI_ReadBinary { chunkSize?: number; } /** - * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.StreamText + * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.ReadText */ -export interface StreamingAPI_StreamText { +export interface StreamingAPI_ReadText { /** - * of Stream resource, that keeps info on item to be streamed. + * of Stream resource that keeps info on item to be streamed. * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; /** - * makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * Client can just use the value of the last response from server to continue streaming after reconnection. - * - * @generated from protobuf field: int64 offset = 2 - */ - offset: bigint; - /** - * allows client to limit total data sent from server. - * This limit is aggregation of all data, sent in all chunks, measured - * in lines of text. - * E.g. to read top 1000 lines from stream source, use = 1000. - * When both and / are set, the is applied first. - * this is equivalent to 'head -n | grep '. - * - * @generated from protobuf field: optional int64 read_limit = 20 - */ - readLimit?: bigint; - /** - * is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. - * - * @generated from protobuf field: optional string search = 21 - */ - search?: string; - /** - * is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. - * - * @generated from protobuf field: optional string search_re = 22 - */ - searchRe?: string; -} -/** - * @generated from protobuf message MiLaboratories.Controller.Shared.StreamingAPI.ReadText - */ -export interface StreamingAPI_ReadText { - /** - * of Stream resource, that keeps info on item to be streamed. + * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: uint64 resource_id = 1 + * @generated from protobuf field: bytes resource_signature = 3 */ - resourceId: bigint; + resourceSignature: Uint8Array; /** - * makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * Client can just use the value of the last response from server to continue streaming after reconnection. + * makes the streamer perform a seek operation to the given offset before sending the contents. + * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + * The client can just use the value of the last response from the server to continue streaming after reconnection. * * @generated from protobuf field: int64 offset = 2 */ offset: bigint; /** - * allows client to limit total data sent from server. + * allows the client to limit total data sent from the server. * Measured in lines of text. * E.g. to read top 1000 lines from stream source, use = 1000. * When both and / are set, the is applied first. - * this is equivalent to 'head -n | grep '. - * At most 3.9 MiB (3900 * 1024 KiB) of data is returned in single read regardless of option + * This is equivalent to 'head -n | grep '. + * At most 3.9 MiB (3900 KiB) of data is returned in a single read regardless of the option * Only full lines of text are returned except for the last line from the completed source - * (the one that is not expected to have new data, like blob in storage) + * (the one that is not expected to have new data, like a blob in storage) * * @generated from protobuf field: optional int64 read_limit = 20 */ readLimit?: bigint; /** - * is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. + * is a substring for the line search pattern. + * This option makes the controller send to the client only lines that + * have the given substring. * * @generated from protobuf field: optional string search = 21 */ search?: string; /** - * is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. + * is a regular expression for the line search pattern. + * This option makes the controller send to the client only lines that + * match the given regular expression. * * @generated from protobuf field: optional string search_re = 22 */ @@ -178,39 +107,45 @@ export interface StreamingAPI_ReadText { */ export interface StreamingAPI_LastLines { /** - * of Stream resource, that keeps info on item to be streamed. + * of Stream resource that keeps info on item to be streamed. * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; /** - * makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * By default, LastLines starts to treat the data source from the very last byte available in data stream - * at the moment of call, but client can set the server to start from earlier position. + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; + /** + * makes the streamer perform a seek operation to the given offset before sending the contents. + * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + * By default, LastLines starts reading the data source from the very last byte available in the data stream + * at the moment of the call, but the client can set the server to start from an earlier position. * * @generated from protobuf field: optional int64 offset = 2 */ offset?: bigint; /** - * makes streamer to return up to lines to the client. + * makes the streamer return up to lines to the client. * Default value: 1 * * @generated from protobuf field: optional int32 line_count = 3 */ lineCount?: number; /** - * is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. + * is a substring for the line search pattern. + * This option makes the controller send to the client only lines that + * have the given substring. * * @generated from protobuf field: optional string search = 21 */ search?: string; /** - * is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. + * is a regular expression for the line search pattern. + * This option makes the controller send to the client only lines that + * match the given regular expression. * * @generated from protobuf field: optional string search_re = 22 */ @@ -221,16 +156,16 @@ export interface StreamingAPI_LastLines { */ export interface StreamingAPI_Response { /** - * data chunk from item, starting from the of the previous message in the same stream. + * data chunk from the item, starting from the of the previous message in the same stream. * * @generated from protobuf field: bytes data = 1 */ data: Uint8Array; /** * is the actual size of the streamed item at the moment of this message. - * This might be not a final amount of streamed data, as stream source can be updated - * by other independent process (e.g., data is written to log file). - * This field in combination with shows, how far the client is from the end + * This might not be the final amount of streamed data, as the stream source can be updated + * by another independent process (e.g., data is written to a log file). + * This field in combination with shows how far the client is from the end * of the data right now. * * @generated from protobuf field: uint64 size = 2 @@ -238,9 +173,9 @@ export interface StreamingAPI_Response { size: bigint; /** * is the new offset in bytes from the start of the streamed item, - * including size of in current response. - * Call to Stream rpc with = will continue - * streaming from the place of last received message + * including the size of in the current response. + * A call to the Stream RPC with = will continue + * streaming from the place of the last received message * (e.g. = - 1 will repeat the last byte of * previously received ) * @@ -287,79 +222,11 @@ class StreamingAPI$Type extends MessageType { */ export const StreamingAPI = new StreamingAPI$Type(); // @generated message type with reflection information, may provide speed optimized methods -class StreamingAPI_StreamBinary$Type extends MessageType { - constructor() { - super("MiLaboratories.Controller.Shared.StreamingAPI.StreamBinary", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 11, name: "chunk_size", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ }, - { no: 20, name: "read_limit", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ } - ]); - } - create(value?: PartialMessage): StreamingAPI_StreamBinary { - const message = globalThis.Object.create((this.messagePrototype!)); - message.resourceId = 0n; - message.offset = 0n; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StreamingAPI_StreamBinary): StreamingAPI_StreamBinary { - let message = target ?? this.create(), end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* uint64 resource_id */ 1: - message.resourceId = reader.uint64().toBigInt(); - break; - case /* int64 offset */ 2: - message.offset = reader.int64().toBigInt(); - break; - case /* optional uint32 chunk_size */ 11: - message.chunkSize = reader.uint32(); - break; - case /* optional int64 read_limit */ 20: - message.readLimit = reader.int64().toBigInt(); - break; - default: - let u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); - } - } - return message; - } - internalBinaryWrite(message: StreamingAPI_StreamBinary, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* uint64 resource_id = 1; */ - if (message.resourceId !== 0n) - writer.tag(1, WireType.Varint).uint64(message.resourceId); - /* int64 offset = 2; */ - if (message.offset !== 0n) - writer.tag(2, WireType.Varint).int64(message.offset); - /* optional uint32 chunk_size = 11; */ - if (message.chunkSize !== undefined) - writer.tag(11, WireType.Varint).uint32(message.chunkSize); - /* optional int64 read_limit = 20; */ - if (message.readLimit !== undefined) - writer.tag(20, WireType.Varint).int64(message.readLimit); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); - return writer; - } -} -/** - * @generated MessageType for protobuf message MiLaboratories.Controller.Shared.StreamingAPI.StreamBinary - */ -export const StreamingAPI_StreamBinary = new StreamingAPI_StreamBinary$Type(); -// @generated message type with reflection information, may provide speed optimized methods class StreamingAPI_ReadBinary$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.ReadBinary", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 11, name: "chunk_size", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ } ]); @@ -367,6 +234,7 @@ class StreamingAPI_ReadBinary$Type extends MessageType create(value?: PartialMessage): StreamingAPI_ReadBinary { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.offset = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -380,6 +248,9 @@ class StreamingAPI_ReadBinary$Type extends MessageType case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 3: + message.resourceSignature = reader.bytes(); + break; case /* int64 offset */ 2: message.offset = reader.int64().toBigInt(); break; @@ -404,6 +275,9 @@ class StreamingAPI_ReadBinary$Type extends MessageType /* int64 offset = 2; */ if (message.offset !== 0n) writer.tag(2, WireType.Varint).int64(message.offset); + /* bytes resource_signature = 3; */ + if (message.resourceSignature.length) + writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional uint32 chunk_size = 11; */ if (message.chunkSize !== undefined) writer.tag(11, WireType.Varint).uint32(message.chunkSize); @@ -418,86 +292,11 @@ class StreamingAPI_ReadBinary$Type extends MessageType */ export const StreamingAPI_ReadBinary = new StreamingAPI_ReadBinary$Type(); // @generated message type with reflection information, may provide speed optimized methods -class StreamingAPI_StreamText$Type extends MessageType { - constructor() { - super("MiLaboratories.Controller.Shared.StreamingAPI.StreamText", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 20, name: "read_limit", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 21, name: "search", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, - { no: 22, name: "search_re", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ } - ]); - } - create(value?: PartialMessage): StreamingAPI_StreamText { - const message = globalThis.Object.create((this.messagePrototype!)); - message.resourceId = 0n; - message.offset = 0n; - if (value !== undefined) - reflectionMergePartial(this, message, value); - return message; - } - internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: StreamingAPI_StreamText): StreamingAPI_StreamText { - let message = target ?? this.create(), end = reader.pos + length; - while (reader.pos < end) { - let [fieldNo, wireType] = reader.tag(); - switch (fieldNo) { - case /* uint64 resource_id */ 1: - message.resourceId = reader.uint64().toBigInt(); - break; - case /* int64 offset */ 2: - message.offset = reader.int64().toBigInt(); - break; - case /* optional int64 read_limit */ 20: - message.readLimit = reader.int64().toBigInt(); - break; - case /* optional string search */ 21: - message.search = reader.string(); - break; - case /* optional string search_re */ 22: - message.searchRe = reader.string(); - break; - default: - let u = options.readUnknownField; - if (u === "throw") - throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); - let d = reader.skip(wireType); - if (u !== false) - (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); - } - } - return message; - } - internalBinaryWrite(message: StreamingAPI_StreamText, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { - /* uint64 resource_id = 1; */ - if (message.resourceId !== 0n) - writer.tag(1, WireType.Varint).uint64(message.resourceId); - /* int64 offset = 2; */ - if (message.offset !== 0n) - writer.tag(2, WireType.Varint).int64(message.offset); - /* optional int64 read_limit = 20; */ - if (message.readLimit !== undefined) - writer.tag(20, WireType.Varint).int64(message.readLimit); - /* optional string search = 21; */ - if (message.search !== undefined) - writer.tag(21, WireType.LengthDelimited).string(message.search); - /* optional string search_re = 22; */ - if (message.searchRe !== undefined) - writer.tag(22, WireType.LengthDelimited).string(message.searchRe); - let u = options.writeUnknownFields; - if (u !== false) - (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); - return writer; - } -} -/** - * @generated MessageType for protobuf message MiLaboratories.Controller.Shared.StreamingAPI.StreamText - */ -export const StreamingAPI_StreamText = new StreamingAPI_StreamText$Type(); -// @generated message type with reflection information, may provide speed optimized methods class StreamingAPI_ReadText$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.ReadText", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 20, name: "read_limit", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 21, name: "search", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, @@ -507,6 +306,7 @@ class StreamingAPI_ReadText$Type extends MessageType { create(value?: PartialMessage): StreamingAPI_ReadText { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.offset = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -520,6 +320,9 @@ class StreamingAPI_ReadText$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 3: + message.resourceSignature = reader.bytes(); + break; case /* int64 offset */ 2: message.offset = reader.int64().toBigInt(); break; @@ -550,6 +353,9 @@ class StreamingAPI_ReadText$Type extends MessageType { /* int64 offset = 2; */ if (message.offset !== 0n) writer.tag(2, WireType.Varint).int64(message.offset); + /* bytes resource_signature = 3; */ + if (message.resourceSignature.length) + writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional int64 read_limit = 20; */ if (message.readLimit !== undefined) writer.tag(20, WireType.Varint).int64(message.readLimit); @@ -574,6 +380,7 @@ class StreamingAPI_LastLines$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.LastLines", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 3, name: "line_count", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }, { no: 21, name: "search", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, @@ -583,6 +390,7 @@ class StreamingAPI_LastLines$Type extends MessageType { create(value?: PartialMessage): StreamingAPI_LastLines { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -595,6 +403,9 @@ class StreamingAPI_LastLines$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 4: + message.resourceSignature = reader.bytes(); + break; case /* optional int64 offset */ 2: message.offset = reader.int64().toBigInt(); break; @@ -628,6 +439,9 @@ class StreamingAPI_LastLines$Type extends MessageType { /* optional int32 line_count = 3; */ if (message.lineCount !== undefined) writer.tag(3, WireType.Varint).int32(message.lineCount); + /* bytes resource_signature = 4; */ + if (message.resourceSignature.length) + writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional string search = 21; */ if (message.search !== undefined) writer.tag(21, WireType.LengthDelimited).string(message.search); @@ -711,9 +525,7 @@ export const StreamingAPI_Response = new StreamingAPI_Response$Type(); * @generated ServiceType for protobuf service MiLaboratories.Controller.Shared.Streaming */ export const Streaming = new ServiceType("MiLaboratories.Controller.Shared.Streaming", [ - { name: "StreamBinary", serverStreaming: true, options: { "google.api.http": { post: "/v1/stream/binary", body: "*" } }, I: StreamingAPI_StreamBinary, O: StreamingAPI_Response }, { name: "ReadBinary", options: { "google.api.http": { post: "/v1/read/binary", body: "*" } }, I: StreamingAPI_ReadBinary, O: StreamingAPI_Response }, - { name: "StreamText", serverStreaming: true, options: { "google.api.http": { post: "/v1/stream/text", body: "*" } }, I: StreamingAPI_StreamText, O: StreamingAPI_Response }, { name: "ReadText", options: { "google.api.http": { post: "/v1/read/text", body: "*" } }, I: StreamingAPI_ReadText, O: StreamingAPI_Response }, { name: "LastLines", options: { "google.api.http": { post: "/v1/last-lines", body: "*" } }, I: StreamingAPI_LastLines, O: StreamingAPI_Response } ]); diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts index 4a215fe61b..14226c33c3 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.client.ts @@ -4,6 +4,8 @@ import type { RpcTransport } from "@protobuf-ts/runtime-rpc"; import type { ServiceInfo } from "@protobuf-ts/runtime-rpc"; import { Upload } from "./protocol"; +import type { UploadAPI_Reset_Response } from "./protocol"; +import type { UploadAPI_Reset_Request } from "./protocol"; import type { UploadAPI_Finalize_Response } from "./protocol"; import type { UploadAPI_Finalize_Request } from "./protocol"; import type { UploadAPI_UpdateProgress_Response } from "./protocol"; @@ -17,7 +19,7 @@ import type { UnaryCall } from "@protobuf-ts/runtime-rpc"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; /** * - * Upload provides access to data upload feature, allowing clients to uplad data to Platforma. + * Upload provides access to the data upload feature, allowing clients to upload data to Platforma. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Upload @@ -25,7 +27,7 @@ import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; export interface IUploadClient { /** * - * Init upload, making controller to do all required preparation steps. + * Initialize the upload, making the controller do all required preparation steps. * * * @generated from protobuf rpc: Init @@ -33,7 +35,7 @@ export interface IUploadClient { init(input: UploadAPI_Init_Request, options?: RpcOptions): UnaryCall; /** * - * GetPartURL provides URL for uploading chunk of the data. + * GetPartURL provides a URL for uploading a chunk of the data. * Clients are expected to put their data directly to the given location. * * @@ -42,7 +44,7 @@ export interface IUploadClient { getPartURL(input: UploadAPI_GetPartURL_Request, options?: RpcOptions): UnaryCall; /** * - * UpdateProgress of the upload, so other clients can see how it is going. + * Update the progress of the upload, so other clients can see how it is going. * * * @generated from protobuf rpc: UpdateProgress @@ -50,18 +52,29 @@ export interface IUploadClient { updateProgress(input: UploadAPI_UpdateProgress_Request, options?: RpcOptions): UnaryCall; /** * - * Finalize informs Controller that the upload process is done. - * Returns an error, if the total size of all uploaded chunks is not equal to - * size of the upload given in Init. + * Finalize informs the Controller that the upload process is done. + * Returns an error if the total size of all uploaded chunks is not equal to + * the size of the upload given in Init. * * * @generated from protobuf rpc: Finalize */ finalize(input: UploadAPI_Finalize_Request, options?: RpcOptions): UnaryCall; + /** + * + * Reset the current upload progress (remove uploaded parts and so on) to start over. + * This is useful when a checksum mismatch is detected by the client during upload + * and the data requires a full re-upload. + * The server never resets the upload on its own; it is always the client's initiative. + * + * + * @generated from protobuf rpc: Reset + */ + reset(input: UploadAPI_Reset_Request, options?: RpcOptions): UnaryCall; } /** * - * Upload provides access to data upload feature, allowing clients to uplad data to Platforma. + * Upload provides access to the data upload feature, allowing clients to upload data to Platforma. * * * @generated from protobuf service MiLaboratories.Controller.Shared.Upload @@ -74,7 +87,7 @@ export class UploadClient implements IUploadClient, ServiceInfo { } /** * - * Init upload, making controller to do all required preparation steps. + * Initialize the upload, making the controller do all required preparation steps. * * * @generated from protobuf rpc: Init @@ -85,7 +98,7 @@ export class UploadClient implements IUploadClient, ServiceInfo { } /** * - * GetPartURL provides URL for uploading chunk of the data. + * GetPartURL provides a URL for uploading a chunk of the data. * Clients are expected to put their data directly to the given location. * * @@ -97,7 +110,7 @@ export class UploadClient implements IUploadClient, ServiceInfo { } /** * - * UpdateProgress of the upload, so other clients can see how it is going. + * Update the progress of the upload, so other clients can see how it is going. * * * @generated from protobuf rpc: UpdateProgress @@ -108,9 +121,9 @@ export class UploadClient implements IUploadClient, ServiceInfo { } /** * - * Finalize informs Controller that the upload process is done. - * Returns an error, if the total size of all uploaded chunks is not equal to - * size of the upload given in Init. + * Finalize informs the Controller that the upload process is done. + * Returns an error if the total size of all uploaded chunks is not equal to + * the size of the upload given in Init. * * * @generated from protobuf rpc: Finalize @@ -119,4 +132,18 @@ export class UploadClient implements IUploadClient, ServiceInfo { const method = this.methods[3], opt = this._transport.mergeOptions(options); return stackIntercept("unary", this._transport, method, opt, input); } + /** + * + * Reset the current upload progress (remove uploaded parts and so on) to start over. + * This is useful when a checksum mismatch is detected by the client during upload + * and the data requires a full re-upload. + * The server never resets the upload on its own; it is always the client's initiative. + * + * + * @generated from protobuf rpc: Reset + */ + reset(input: UploadAPI_Reset_Request, options?: RpcOptions): UnaryCall { + const method = this.methods[4], opt = this._transport.mergeOptions(options); + return stackIntercept("unary", this._transport, method, opt, input); + } } diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts index f9b354d954..d303cddd4f 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts @@ -26,11 +26,17 @@ export interface UploadAPI_Init { */ export interface UploadAPI_Init_Request { /** - * Id of upload resource + * ID of the upload resource * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Init.Response @@ -38,11 +44,11 @@ export interface UploadAPI_Init_Request { export interface UploadAPI_Init_Response { /** * Number of parts in this upload. - * For parallel upload support, client can generate any number of part upload URLs - * at the moment and upload them in parallel. + * For parallel upload support, the client can generate any number of part upload URLs + * at once and upload them in parallel. * keeps the number of chunks supported by this upload. * The parts count is calculated from the planned size of the upload, controller - * configuration and underlying storage restrictions. + * configuration, and underlying storage restrictions. * * @generated from protobuf field: uint64 parts_count = 1 */ @@ -52,22 +58,23 @@ export interface UploadAPI_Init_Response { */ partSize: bigint; /** - * Checksum algorithm to use for the part upload. + * Checksum algorithm to use for each part upload and final verification. * * @generated from protobuf field: MiLaboratories.Controller.Shared.UploadAPI.ChecksumAlgorithm checksum_algorithm = 4 */ checksumAlgorithm: UploadAPI_ChecksumAlgorithm; /** - * Header name to use for the checksum. + * Header name to use for the checksum in each part upload request. + * A non-empty value combined with checksum_algorithm enables each part upload verification. * * @generated from protobuf field: string checksum_header = 5 */ checksumHeader: string; /** - * List of IDs of parts that were already uploaded by client. - * Helps client to recover upload and skip already done parts + * List of IDs of parts that were already uploaded by the client. + * Helps the client recover the upload and skip already uploaded parts * after being interrupted in the middle of the upload - * (say, because of the restart). + * (say, because of a restart). * Parts enumeration starts from 1. * * @generated from protobuf field: repeated uint64 uploaded_parts = 2 @@ -84,21 +91,27 @@ export interface UploadAPI_UpdateProgress { */ export interface UploadAPI_UpdateProgress_Request { /** - * Id of upload resource + * ID of the upload resource * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; /** - * Amount of bytes, uploaded since the earlier call to UpdateProgress. - * This value is just blindly added to the 'bytes_processed' of progress report, + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 3 + */ + resourceSignature: Uint8Array; + /** + * Number of bytes uploaded since the earlier call to UpdateProgress. + * This value is just blindly added to the 'bytes_processed' of the progress report, * so other clients can see the upload progress. - * If client uploads the data in several streams (several chunks in parallel), it + * If the client uploads the data in several streams (several chunks in parallel), it * can safely send progress updates individually for each of the streams, just counting - * bytes uploaded by particular stream. + * bytes uploaded by a particular stream. * - * Negative value can be used to report about upload retry: when upload was interrupted, - * part of the uploaded data is lost and require re-upload. + * A negative value can be used to report an upload retry: when the upload was interrupted, + * part of the uploaded data is lost and requires a re-upload. * * @generated from protobuf field: int64 bytes_processed = 2 */ @@ -119,15 +132,21 @@ export interface UploadAPI_GetPartURL { */ export interface UploadAPI_GetPartURL_Request { /** - * Id of upload resource + * ID of the upload resource * * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; /** - * Part to be uploaded. It is responsibility of the Client to watch after already uploaded parts: - * - client can request an URL for the same part twice (request -> request) without errors; - * - client can request an URL for alrady uploaded part (request -> upload -> request) without errors. + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 6 + */ + resourceSignature: Uint8Array; + /** + * Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: + * - client can request a URL for the same part twice (request -> request) without errors; + * - client can request a URL for an already uploaded part (request -> upload -> request) without errors. * * Parts enumeration starts from 1. * @@ -135,24 +154,24 @@ export interface UploadAPI_GetPartURL_Request { */ partNumber: bigint; /** - * Size of the part uploaded by client earlier. Allows controller to count upload progress - * based on client's input. - * Client is free to never sent this value (send zeroes in each request). + * Size of the part uploaded by the client earlier. Allows the controller to count upload progress + * based on the client's input. + * The client is free to never send this value (send zeroes in each request). * * @generated from protobuf field: uint64 uploaded_part_size = 3 */ uploadedPartSize: bigint; /** - * Do we need to presign URL for internal use. - * Controllers could use this if they are trying to download something from internal network. - * For backward compatibility, by default pl backend will presign external urls. + * Whether to presign the URL for internal use. + * Controllers could use this if they are trying to upload something from the internal network. + * For backward compatibility, by default the pl backend will presign external URLs. * * @generated from protobuf field: bool is_internal_use = 4 */ isInternalUse: boolean; /** - * Checksum is not used for now, but it is here for case - * where signing checksum header is required. + * Checksum is not used for now, but it is here for the case + * where signing the checksum header is required. * * @generated from protobuf field: string part_checksum = 5 */ @@ -163,11 +182,11 @@ export interface UploadAPI_GetPartURL_Request { */ export interface UploadAPI_GetPartURL_HTTPHeader { /** - * @generated from protobuf field: string Name = 1 + * @generated from protobuf field: string name = 1 */ name: string; /** - * @generated from protobuf field: string Value = 2 + * @generated from protobuf field: string value = 2 */ value: string; } @@ -188,9 +207,9 @@ export interface UploadAPI_GetPartURL_Response { */ method: string; /** - * List of headers with their values, MANDATORY to be sent by the client for the upload. - * The destination service (the one, that will handle upload request for specific part) - * may reject the request if it would not keep the given headers. + * List of headers with their values that MUST be sent by the client for the upload. + * The destination service (the one that will handle the upload request for a specific part) + * may reject the request if it does not include the given headers. * * @generated from protobuf field: repeated MiLaboratories.Controller.Shared.UploadAPI.GetPartURL.HTTPHeader headers = 3 */ @@ -198,7 +217,7 @@ export interface UploadAPI_GetPartURL_Response { /** * The number of the _first_ byte in the chunk. * Absolute position from the start of the file ( file.seek(, SEEK_START) ). - * The client is expected to send [; ) range. + * The client is expected to send the [; ) range. * * @generated from protobuf field: uint64 chunk_start = 4 */ @@ -206,7 +225,7 @@ export interface UploadAPI_GetPartURL_Response { /** * The number of the byte _after_ the last to be sent in the chunk. * Absolute position from the start of the file. - * The client is expected to send [; ) range. + * The client is expected to send the [; ) range. * * @generated from protobuf field: uint64 chunk_end = 5 */ @@ -225,12 +244,60 @@ export interface UploadAPI_Finalize_Request { * @generated from protobuf field: uint64 resource_id = 1 */ resourceId: bigint; + /** + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 4 + */ + resourceSignature: Uint8Array; + /** + * The client can send the final checksum of the whole file to verify the upload. + * The algorithm used to calculate this final checksum must match the algorithm from + * Init.Response. The server may reject the request on a mismatch + * (meaning the server does not support the given algorithm). + * + * @generated from protobuf field: MiLaboratories.Controller.Shared.UploadAPI.ChecksumAlgorithm checksum_algorithm = 2 + */ + checksumAlgorithm: UploadAPI_ChecksumAlgorithm; + /** + * Checksum of the whole file for validation. When provided, the server may verify the upload before + * marking it final. This is optional behavior that depends on the particular storage driver + * used on the backend side for this upload. See the storage driver's implementation for more details. + * + * @generated from protobuf field: bytes checksum = 3 + */ + checksum: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Finalize.Response */ export interface UploadAPI_Finalize_Response { } +/** + * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset + */ +export interface UploadAPI_Reset { +} +/** + * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset.Request + */ +export interface UploadAPI_Reset_Request { + /** + * @generated from protobuf field: uint64 resource_id = 1 + */ + resourceId: bigint; + /** + * Signature proving the caller is authorized to access this resource. + * + * @generated from protobuf field: bytes resource_signature = 2 + */ + resourceSignature: Uint8Array; +} +/** + * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset.Response + */ +export interface UploadAPI_Reset_Response { +} /** * @generated from protobuf enum MiLaboratories.Controller.Shared.UploadAPI.ChecksumAlgorithm */ @@ -324,12 +391,14 @@ export const UploadAPI_Init = new UploadAPI_Init$Type(); class UploadAPI_Init_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.UploadAPI.Init.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): UploadAPI_Init_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -342,6 +411,9 @@ class UploadAPI_Init_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; default: let u = options.readUnknownField; if (u === "throw") @@ -357,6 +429,9 @@ class UploadAPI_Init_Request$Type extends MessageType { /* uint64 resource_id = 1; */ if (message.resourceId !== 0n) writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); @@ -497,12 +572,14 @@ class UploadAPI_UpdateProgress_Request$Type extends MessageType): UploadAPI_UpdateProgress_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.bytesProcessed = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -516,6 +593,9 @@ class UploadAPI_UpdateProgress_Request$Type extends MessageType): UploadAPI_GetPartURL_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); message.partNumber = 0n; message.uploadedPartSize = 0n; message.isInternalUse = false; @@ -653,6 +738,9 @@ class UploadAPI_GetPartURL_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.UploadAPI.GetPartURL.HTTPHeader", [ - { no: 1, name: "Name", kind: "scalar", jsonName: "Name", T: 9 /*ScalarType.STRING*/ }, - { no: 2, name: "Value", kind: "scalar", jsonName: "Value", T: 9 /*ScalarType.STRING*/ } + { no: 1, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, + { no: 2, name: "value", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): UploadAPI_GetPartURL_HTTPHeader { @@ -723,10 +814,10 @@ class UploadAPI_GetPartURL_HTTPHeader$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.UploadAPI.Finalize.Request", [ - { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ } + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 2, name: "checksum_algorithm", kind: "enum", T: () => ["MiLaboratories.Controller.Shared.UploadAPI.ChecksumAlgorithm", UploadAPI_ChecksumAlgorithm, "CHECKSUM_ALGORITHM_"] }, + { no: 3, name: "checksum", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): UploadAPI_Finalize_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + message.checksumAlgorithm = 0; + message.checksum = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -896,6 +993,15 @@ class UploadAPI_Finalize_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.Controller.Shared.UploadAPI.Reset", []); + } + create(value?: PartialMessage): UploadAPI_Reset { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UploadAPI_Reset): UploadAPI_Reset { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UploadAPI_Reset, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset + */ +export const UploadAPI_Reset = new UploadAPI_Reset$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UploadAPI_Reset_Request$Type extends MessageType { + constructor() { + super("MiLaboratories.Controller.Shared.UploadAPI.Reset.Request", [ + { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, + { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + ]); + } + create(value?: PartialMessage): UploadAPI_Reset_Request { + const message = globalThis.Object.create((this.messagePrototype!)); + message.resourceId = 0n; + message.resourceSignature = new Uint8Array(0); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UploadAPI_Reset_Request): UploadAPI_Reset_Request { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + case /* uint64 resource_id */ 1: + message.resourceId = reader.uint64().toBigInt(); + break; + case /* bytes resource_signature */ 2: + message.resourceSignature = reader.bytes(); + break; + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UploadAPI_Reset_Request, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + /* uint64 resource_id = 1; */ + if (message.resourceId !== 0n) + writer.tag(1, WireType.Varint).uint64(message.resourceId); + /* bytes resource_signature = 2; */ + if (message.resourceSignature.length) + writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset.Request + */ +export const UploadAPI_Reset_Request = new UploadAPI_Reset_Request$Type(); +// @generated message type with reflection information, may provide speed optimized methods +class UploadAPI_Reset_Response$Type extends MessageType { + constructor() { + super("MiLaboratories.Controller.Shared.UploadAPI.Reset.Response", []); + } + create(value?: PartialMessage): UploadAPI_Reset_Response { + const message = globalThis.Object.create((this.messagePrototype!)); + if (value !== undefined) + reflectionMergePartial(this, message, value); + return message; + } + internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: UploadAPI_Reset_Response): UploadAPI_Reset_Response { + let message = target ?? this.create(), end = reader.pos + length; + while (reader.pos < end) { + let [fieldNo, wireType] = reader.tag(); + switch (fieldNo) { + default: + let u = options.readUnknownField; + if (u === "throw") + throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`); + let d = reader.skip(wireType); + if (u !== false) + (u === true ? UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d); + } + } + return message; + } + internalBinaryWrite(message: UploadAPI_Reset_Response, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter { + let u = options.writeUnknownFields; + if (u !== false) + (u == true ? UnknownFieldHandler.onWrite : u)(this.typeName, message, writer); + return writer; + } +} +/** + * @generated MessageType for protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset.Response + */ +export const UploadAPI_Reset_Response = new UploadAPI_Reset_Response$Type(); /** * @generated ServiceType for protobuf service MiLaboratories.Controller.Shared.Upload */ @@ -966,5 +1212,6 @@ export const Upload = new ServiceType("MiLaboratories.Controller.Shared.Upload", { name: "Init", options: { "google.api.http": { post: "/v1/upload/init", body: "*" } }, I: UploadAPI_Init_Request, O: UploadAPI_Init_Response }, { name: "GetPartURL", options: { "google.api.http": { post: "/v1/upload/get-part-url", body: "*" } }, I: UploadAPI_GetPartURL_Request, O: UploadAPI_GetPartURL_Response }, { name: "UpdateProgress", options: { "google.api.http": { post: "/v1/upload/update-progress", body: "*" } }, I: UploadAPI_UpdateProgress_Request, O: UploadAPI_UpdateProgress_Response }, - { name: "Finalize", options: { "google.api.http": { post: "/v1/upload/finalize", body: "*" } }, I: UploadAPI_Finalize_Request, O: UploadAPI_Finalize_Response } + { name: "Finalize", options: { "google.api.http": { post: "/v1/upload/finalize", body: "*" } }, I: UploadAPI_Finalize_Request, O: UploadAPI_Finalize_Response }, + { name: "Reset", options: { "google.api.http": { post: "/v1/upload/reset", body: "*" } }, I: UploadAPI_Reset_Request, O: UploadAPI_Reset_Response } ]); diff --git a/lib/node/pl-drivers/src/proto-grpc/google/protobuf/descriptor.ts b/lib/node/pl-drivers/src/proto-grpc/google/protobuf/descriptor.ts index 7425d2d606..b738fff32d 100644 --- a/lib/node/pl-drivers/src/proto-grpc/google/protobuf/descriptor.ts +++ b/lib/node/pl-drivers/src/proto-grpc/google/protobuf/descriptor.ts @@ -1260,8 +1260,6 @@ export enum FieldOptions_JSType { } /** * If set to RETENTION_SOURCE, the option will be omitted from the binary. - * Note: as of January 2023, support for this is in progress and does not yet - * have an effect (b/264593489). * * @generated from protobuf enum google.protobuf.FieldOptions.OptionRetention */ @@ -1282,8 +1280,7 @@ export enum FieldOptions_OptionRetention { /** * This indicates the types of entities that the field may apply to when used * as an option. If it is unset, then the field may be freely used as an - * option on any kind of entity. Note: as of January 2023, support for this is - * in progress and does not yet have an effect (b/264593489). + * option on any kind of entity. * * @generated from protobuf enum google.protobuf.FieldOptions.OptionTargetType */ @@ -2070,7 +2067,7 @@ export enum Edition { EDITION_2024 = 1001, /** * Placeholder editions for testing feature resolution. These should not be - * used or relyed on outside of tests. + * used or relied on outside of tests. * * @generated from protobuf enum value: EDITION_1_TEST_ONLY = 1; */ diff --git a/lib/node/pl-drivers/src/proto-rest/downloadapi.ts b/lib/node/pl-drivers/src/proto-rest/downloadapi.ts index ef8375f305..46e1509e1e 100644 --- a/lib/node/pl-drivers/src/proto-rest/downloadapi.ts +++ b/lib/node/pl-drivers/src/proto-rest/downloadapi.ts @@ -4,102 +4,107 @@ */ export interface paths { - "/v1/get-download-url": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/get-download-url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["Download_GetDownloadURL"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - post: operations["Download_GetDownloadURL"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - GetDownloadURL_HTTPHeader: { - name: string; - value: string; - }; - GetDownloadURL_Request: { - resourceId: string; - /** - * @description Pass `true` here if the blob will be downloaded from internal network, - * e.g. controllers could use this if they are trying to download something from internal network. - * For backward compatibility, by default pl treats all requests as from external network. - */ - isInternalUse: boolean; - }; - GetDownloadURL_Response: { - downloadUrl: string; - headers: components["schemas"]["GetDownloadURL_HTTPHeader"][]; - }; - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; + schemas: { + GetDownloadURL_HTTPHeader: { + name: string; + value: string; + }; + GetDownloadURL_Request: { + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description Pass `true` here if the blob will be downloaded from the internal network, + * e.g. controllers could use this if they are trying to download something from the internal network. + * For backward compatibility, by default pl treats all requests as from the external network. + */ + isInternalUse: boolean; + }; + GetDownloadURL_Response: { + downloadUrl: string; + headers: components["schemas"]["GetDownloadURL_HTTPHeader"][]; + }; + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; + }; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; + }; }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - Download_GetDownloadURL: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetDownloadURL_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetDownloadURL_Response"]; + Download_GetDownloadURL: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["GetDownloadURL_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetDownloadURL_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } diff --git a/lib/node/pl-drivers/src/proto-rest/lsapi.ts b/lib/node/pl-drivers/src/proto-rest/lsapi.ts index 652565389c..84c7ccc294 100644 --- a/lib/node/pl-drivers/src/proto-rest/lsapi.ts +++ b/lib/node/pl-drivers/src/proto-rest/lsapi.ts @@ -4,145 +4,150 @@ */ export interface paths { - "/v1/list": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/list": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["LS_List"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - post: operations["LS_List"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - List_Request: { - /** @description resource_id of 'LS/' resource */ - resourceId: string; - /** - * @description location to list, absolute to storage root. Only items, that have starting - * from are included into list response. - */ - location: string; - }; - List_Response: { - /** - * @description List of the full (absolute to storage root) names of items from storage. - * E.g., for 'fs' storage each name will consist of names of all directories, where the - * item is located, and the item name itself. - * The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. - */ - items: components["schemas"]["LsAPI_ListItem"][]; - /** - * @description delimiter is path separator, used in this storage. Client can use it to parse item names into parts, - * to extract directory names. - */ - delimiter: string; - }; - LsAPI_ListItem: { - /** @description name of the item in storage, without any prefixes */ - name: string; - /** - * @description size of item in bytes - * is always zero for directories (is_dir = true) - */ - size: string; - /** @description is_dir is true for item, that can have subitems. */ - isDir: boolean; - /** - * @description full_name is the name of item absolute to storage root. - * it is + - * The , used in names, is storage-specific and is NOT guaranteed to be '/'. - */ - fullName: string; - /** - * @description directory, the item is located in. The value here is always a prefix of name: - * name.HasPrefix(directory) is always true. - */ - directory: string; - /** - * Format: date-time - * @description last_modified keeps the item last modification timestamp - */ - lastModified: string; - /** - * @description version of item in storage. - * When storage supports versioning or provides checksums for the data stored, - * the field keeps that data. - * If not - it keeps the any simple combination of item attributes, that helps to - * detect if the contents of item has changed, e.g. +. - * Anyway, client should not try to interpret this field, but should provide it to the Platform - * in operations with given item (like BlobImportInternal) to help Platform with deduplication. - */ - version: string; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; + schemas: { + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; + }; + List_Request: { + /** @description resource_id of 'LS/' resource */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description Location to list, relative to the storage root. Only items that have starting + * with are included in the list response. + */ + location: string; + }; + List_Response: { + /** + * @description List of the full (relative to storage root) names of items from storage. + * E.g., for 'fs' storage each name will consist of names of all directories, where the + * item is located, and the item name itself. + * The delimiter, used in names, is storage-specific and is NOT guaranteed to be '/'. + */ + items: components["schemas"]["LsAPI_ListItem"][]; + /** + * @description The delimiter is the path separator used in this storage. The client can use it to parse item names into parts, + * to extract directory names. + */ + delimiter: string; + }; + LsAPI_ListItem: { + /** @description Name of the item in storage, without any prefixes. */ + name: string; + /** + * @description Size of the item in bytes. + * Is always zero for directories (is_dir = true). + */ + size: string; + /** @description is_dir is true for an item that can have subitems. */ + isDir: boolean; + /** + * @description full_name is the full name of the item, relative to the storage root. + * It is + . + * The , used in names, is storage-specific and is NOT guaranteed to be '/'. + */ + fullName: string; + /** + * @description The directory the item is located in. The value here is always a prefix of name: + * name.HasPrefix(directory) is always true. + */ + directory: string; + /** + * Format: date-time + * @description last_modified keeps the item's last modification timestamp. + */ + lastModified: string; + /** + * @description Version of item in storage. + * When the storage supports versioning or provides checksums for the data stored, + * the field keeps that data. + * If not, it keeps any simple combination of item attributes that helps to + * detect if the contents of the item have changed, e.g. +. + * Anyway, the client should not try to interpret this field, but should provide it to the Platform + * in operations with a given item (like BlobImportInternal) to help the Platform with deduplication. + */ + version: string; + }; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; + }; }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - LS_List: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["List_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["List_Response"]; + LS_List: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["List_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["List_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } diff --git a/lib/node/pl-drivers/src/proto-rest/progressapi.ts b/lib/node/pl-drivers/src/proto-rest/progressapi.ts index d46d819a19..fd1bfacdb2 100644 --- a/lib/node/pl-drivers/src/proto-rest/progressapi.ts +++ b/lib/node/pl-drivers/src/proto-rest/progressapi.ts @@ -4,156 +4,160 @@ */ export interface paths { - "/v1/get-progress": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/get-progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["Progress_GetStatus"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - post: operations["Progress_GetStatus"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/realtime-progress": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/realtime-progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + post: operations["Progress_RealtimeStatus"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - post: operations["Progress_RealtimeStatus"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - GetStatus_Request: { - resourceId: string; - }; - GetStatus_Response: { - report: components["schemas"]["ProgressAPI_Report"]; - }; - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - ProgressAPI_Report: { - /** Format: float */ - progress: number; - bytesProcessed: string; - bytesTotal: string; - done: boolean; - /** @description Name of current progress stage (if any) */ - name: string; - }; - RealtimeStatus_Request: { - resourceId: string; - updateInterval: string; - }; - RealtimeStatus_Response: { - report: components["schemas"]["ProgressAPI_Report"]; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; + schemas: { + GetStatus_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + }; + GetStatus_Response: { + report: components["schemas"]["ProgressAPI_Report"]; + }; + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; + }; + ProgressAPI_Report: { + /** Format: float */ + progress: number; + bytesProcessed: string; + bytesTotal: string; + done: boolean; + /** @description Name of current progress stage (if any) */ + name: string; + }; + RealtimeStatus_Request: { + resourceId: string; + /** Format: bytes */ + resourceSignature: string; + updateInterval: string; + }; + RealtimeStatus_Response: { + report: components["schemas"]["ProgressAPI_Report"]; + }; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; + }; }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - Progress_GetStatus: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetStatus_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["GetStatus_Response"]; + Progress_GetStatus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["GetStatus_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetStatus_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; - }; - }; - Progress_RealtimeStatus: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["RealtimeStatus_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["RealtimeStatus_Response"]; + Progress_RealtimeStatus: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["RealtimeStatus_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["RealtimeStatus_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } diff --git a/lib/node/pl-drivers/src/proto-rest/streamingapi.ts b/lib/node/pl-drivers/src/proto-rest/streamingapi.ts index acb0943815..7e420fcc87 100644 --- a/lib/node/pl-drivers/src/proto-rest/streamingapi.ts +++ b/lib/node/pl-drivers/src/proto-rest/streamingapi.ts @@ -4,466 +4,320 @@ */ export interface paths { - "/v1/last-lines": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * @description LastLines provides single message with the last lines from data source. - * When search pattern is specified, the last lines matching the given pattern are returned. - * The lines are returned in reversed order, as server reads data source from the end. - * Consider it as equivalent to 'tac | grep | head -n ' - * The returned in the response points to the _beginning_ of the last - * line found, so client can continue reading the file backwards in subsequent calls. - * This means, that use of this in ReadText() will return you the same line - * returned last in LastLines() data. - */ - post: operations["Streaming_LastLines"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/read/binary": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - get?: never; - put?: never; - /** - * @description ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. - */ - post: operations["Streaming_ReadBinary"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/read/text": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/last-lines": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description LastLines provides a single message with the last lines from the data source. + * When a search pattern is specified, the last lines matching the given pattern are returned. + * The lines are returned in reverse order, as the server reads the data source from the end. + * Consider it equivalent to 'tac | grep | head -n ' + * The returned in the response points to the _beginning_ of the last + * line found, so the client can continue reading the file backwards in subsequent calls. + * This means that the use of this in ReadText() will return the last line + * returned in the LastLines() response. + */ + post: operations["Streaming_LastLines"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * @description ReadBinary allows to read remote item in chunks using stream-like API. - * The difference to StreamBinary is that the client receives single response for each - * call and has to send new calls to the server to get fresh data from remote item. - * Each response (each chunk from server) keeps not more than 3.9MiB of data. - */ - post: operations["Streaming_ReadText"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/stream/binary": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/read/binary": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description ReadBinary allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. + */ + post: operations["Streaming_ReadBinary"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * @description StreamBinary provides stream of binary file. Each response message keeps - * one single chunk of binary data from data source. See StreamingAPI.Binary message - * for more info on available options. - */ - post: operations["Streaming_StreamBinary"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/stream/text": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/read/text": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description ReadText allows reading a remote item in chunks using a stream-like API. + * The client receives a single response for each call and has to send new calls + * to the server to get fresh data from the remote item. + * Each response (each chunk from the server) contains no more than 3.9MiB of data. + */ + post: operations["Streaming_ReadText"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * @description StreamText provides stream of textual file, splitting the data by newline symbol. - * Each response message keeps one single line of text from data source. - */ - post: operations["Streaming_StreamText"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; - }; - StreamingAPI_LastLines: { - /** @description of Stream resource, that keeps info on item to be streamed. */ - resourceId: string; - /** - * @description makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * By default, LastLines starts to treat the data source from the very last byte available in data stream - * at the moment of call, but client can set the server to start from earlier position. - */ - offset: string; - /** - * Format: int32 - * @description makes streamer to return up to lines to the client. - * Default value: 1 - */ - lineCount: number; - /** - * @description is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. - */ - search: string; - /** - * @description is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. - */ - searchRe: string; - }; - StreamingAPI_ReadBinary: { - /** @description of Stream resource, that keeps info on item to be streamed. */ - resourceId: string; - /** @description makes streamer to perform seek operation to given offset before sending the data. */ - offset: string; - /** - * Format: uint32 - * @description limits the maximum size of for response message in stream. - * - * Default value: 32 768 (32 KiB) - * Max value: 3900 * 1024 (3.9 MiB) - */ - chunkSize: number; - }; - StreamingAPI_ReadText: { - /** @description of Stream resource, that keeps info on item to be streamed. */ - resourceId: string; - /** - * @description makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * Client can just use the value of the last response from server to continue streaming after reconnection. - */ - offset: string; - /** - * @description allows client to limit total data sent from server. - * Measured in lines of text. - * E.g. to read top 1000 lines from stream source, use = 1000. - * When both and / are set, the is applied first. - * this is equivalent to 'head -n | grep '. - * At most 3.9 MiB (3900 * 1024 KiB) of data is returned in single read regardless of option - * Only full lines of text are returned except for the last line from the completed source - * (the one that is not expected to have new data, like blob in storage) - */ - readLimit: string; - /** - * @description is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. - */ - search: string; - /** - * @description is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. - */ - searchRe: string; - }; - StreamingAPI_Response: { - /** - * Format: bytes - * @description data chunk from item, starting from the of the previous message in the same stream. - */ - data: string; - /** - * @description is the actual size of the streamed item at the moment of this message. - * This might be not a final amount of streamed data, as stream source can be updated - * by other independent process (e.g., data is written to log file). - * This field in combination with shows, how far the client is from the end - * of the data right now. - */ - size: string; - /** - * @description is the new offset in bytes from the start of the streamed item, - * including size of in current response. - * Call to Stream rpc with = will continue - * streaming from the place of last received message - * (e.g. = - 1 will repeat the last byte of - * previously received ) - */ - newOffset: string; - }; - StreamingAPI_StreamBinary: { - /** @description of Stream resource, that keeps info on item to be streamed. */ - resourceId: string; - /** @description makes streamer to perform seek operation to given offset before sending the data. */ - offset: string; - /** - * Format: uint32 - * @description limits the maximum size of for each response message in stream. - * - * Default value: 32 768 (32 KiB) - * Max value: 3900 * 1024 (3.9 MiB) - */ - chunkSize: number; - /** - * @description allows client to limit total data sent from server. - * This limit is aggregation of all data, sent in all chunks. - * E.g. to read 2000 bytes of data in chunks of at most - * 130 bytes, use = 130; = 2000. - * For storage item of appropriate size this settings will result in - * 16 messages from server: 15 of 130 bytes and one of 50 bytes. - */ - readLimit: string; - }; - StreamingAPI_StreamText: { - /** @description of Stream resource, that keeps info on item to be streamed. */ - resourceId: string; - /** - * @description makes streamer to perform seek operation to given offset before sending the contents. - * This offset is taken in BYTES, as it eases streaming recovery after client reconnection or controller restart. - * Client can just use the value of the last response from server to continue streaming after reconnection. - */ - offset: string; - /** - * @description allows client to limit total data sent from server. - * This limit is aggregation of all data, sent in all chunks, measured - * in lines of text. - * E.g. to read top 1000 lines from stream source, use = 1000. - * When both and / are set, the is applied first. - * this is equivalent to 'head -n | grep '. - */ - readLimit: string; - /** - * @description is substring for line search pattern. - * This option makes controller to send to the client only lines, that - * have given substring. - */ - search: string; - /** - * @description is regular expression for line search pattern. - * This option makes controller to send to the client only lines, that - * match given regular expression. - */ - searchRe: string; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; -} -export type $defs = Record; -export interface operations { - Streaming_LastLines: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["StreamingAPI_LastLines"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; + schemas: { + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; }; - content: { - "application/json": components["schemas"]["StreamingAPI_Response"]; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + StreamingAPI_LastLines: { + /** @description of Stream resource that keeps info on item to be streamed. */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description makes the streamer perform a seek operation to the given offset before sending the contents. + * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + * By default, LastLines starts reading the data source from the very last byte available in the data stream + * at the moment of the call, but the client can set the server to start from an earlier position. + */ + offset: string; + /** + * Format: int32 + * @description makes the streamer return up to lines to the client. + * Default value: 1 + */ + lineCount: number; + /** + * @description is a substring for the line search pattern. + * This option makes the controller send to the client only lines that + * have the given substring. + */ + search: string; + /** + * @description is a regular expression for the line search pattern. + * This option makes the controller send to the client only lines that + * match the given regular expression. + */ + searchRe: string; }; - content: { - "application/json": components["schemas"]["Status"]; + StreamingAPI_ReadBinary: { + /** @description of Stream resource that keeps info on item to be streamed. */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** @description makes the streamer perform a seek operation to the given offset before sending the data. */ + offset: string; + /** + * Format: uint32 + * @description limits the maximum size of for response message in stream. + * + * Default value: 32 768 (32 KiB) + * Max value: 3900 * 1024 (3.9 MiB) + */ + chunkSize: number; }; - }; - }; - }; - Streaming_ReadBinary: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["StreamingAPI_ReadBinary"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["StreamingAPI_Response"]; - }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + StreamingAPI_ReadText: { + /** @description of Stream resource that keeps info on item to be streamed. */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description makes the streamer perform a seek operation to the given offset before sending the contents. + * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. + * The client can just use the value of the last response from the server to continue streaming after reconnection. + */ + offset: string; + /** + * @description allows the client to limit total data sent from the server. + * Measured in lines of text. + * E.g. to read top 1000 lines from stream source, use = 1000. + * When both and / are set, the is applied first. + * This is equivalent to 'head -n | grep '. + * At most 3.9 MiB (3900 KiB) of data is returned in a single read regardless of the option + * Only full lines of text are returned except for the last line from the completed source + * (the one that is not expected to have new data, like a blob in storage) + */ + readLimit: string; + /** + * @description is a substring for the line search pattern. + * This option makes the controller send to the client only lines that + * have the given substring. + */ + search: string; + /** + * @description is a regular expression for the line search pattern. + * This option makes the controller send to the client only lines that + * match the given regular expression. + */ + searchRe: string; }; - content: { - "application/json": components["schemas"]["Status"]; + StreamingAPI_Response: { + /** + * Format: bytes + * @description data chunk from the item, starting from the of the previous message in the same stream. + */ + data: string; + /** + * @description is the actual size of the streamed item at the moment of this message. + * This might not be the final amount of streamed data, as the stream source can be updated + * by another independent process (e.g., data is written to a log file). + * This field in combination with shows how far the client is from the end + * of the data right now. + */ + size: string; + /** + * @description is the new offset in bytes from the start of the streamed item, + * including the size of in the current response. + * A call to the Stream RPC with = will continue + * streaming from the place of the last received message + * (e.g. = - 1 will repeat the last byte of + * previously received ) + */ + newOffset: string; }; - }; }; - }; - Streaming_ReadText: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["StreamingAPI_ReadText"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["StreamingAPI_Response"]; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} +export type $defs = Record; +export interface operations { + Streaming_LastLines: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["StreamingAPI_LastLines"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamingAPI_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; - }; - }; - Streaming_StreamBinary: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["StreamingAPI_StreamBinary"]; - }; }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; + Streaming_ReadBinary: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - content: { - "application/json": components["schemas"]["StreamingAPI_Response"]; + requestBody: { + content: { + "application/json": components["schemas"]["StreamingAPI_ReadBinary"]; + }; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamingAPI_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Streaming_StreamText: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["StreamingAPI_StreamText"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["StreamingAPI_Response"]; + Streaming_ReadText: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["StreamingAPI_ReadText"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["StreamingAPI_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } diff --git a/lib/node/pl-drivers/src/proto-rest/uploadapi.ts b/lib/node/pl-drivers/src/proto-rest/uploadapi.ts index f82f1f5a89..847df8c72b 100644 --- a/lib/node/pl-drivers/src/proto-rest/uploadapi.ts +++ b/lib/node/pl-drivers/src/proto-rest/uploadapi.ts @@ -4,352 +4,454 @@ */ export interface paths { - "/v1/upload/finalize": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/upload/finalize": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description Finalize informs the Controller that the upload process is done. + * Returns an error if the total size of all uploaded chunks is not equal to + * the size of the upload given in Init. + */ + post: operations["Upload_Finalize"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * @description Finalize informs Controller that the upload process is done. - * Returns an error, if the total size of all uploaded chunks is not equal to - * size of the upload given in Init. - */ - post: operations["Upload_Finalize"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/upload/get-part-url": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/upload/get-part-url": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description GetPartURL provides a URL for uploading a chunk of the data. + * Clients are expected to put their data directly to the given location. + */ + post: operations["Upload_GetPartURL"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** - * @description GetPartURL provides URL for uploading chunk of the data. - * Clients are expected to put their data directly to the given location. - */ - post: operations["Upload_GetPartURL"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/upload/init": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/upload/init": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Initialize the upload, making the controller do all required preparation steps. */ + post: operations["Upload_Init"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; + }; + "/v1/upload/reset": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** + * @description Reset the current upload progress (remove uploaded parts and so on) to start over. + * This is useful when a checksum mismatch is detected by the client during upload + * and the data requires a full re-upload. + * The server never resets the upload on its own; it is always the client's initiative. + */ + post: operations["Upload_Reset"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** @description Init upload, making controller to do all required preparation steps. */ - post: operations["Upload_Init"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; - "/v1/upload/update-progress": { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; + "/v1/upload/update-progress": { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + get?: never; + put?: never; + /** @description Update the progress of the upload, so other clients can see how it is going. */ + post: operations["Upload_UpdateProgress"]; + delete?: never; + options?: never; + head?: never; + patch?: never; + trace?: never; }; - get?: never; - put?: never; - /** @description UpdateProgress of the upload, so other clients can see how it is going. */ - post: operations["Upload_UpdateProgress"]; - delete?: never; - options?: never; - head?: never; - patch?: never; - trace?: never; - }; } export type webhooks = Record; export interface components { - schemas: { - Finalize_Request: { - resourceId: string; - }; - Finalize_Response: Record; - GetPartURL_HTTPHeader: { - name: string; - value: string; - }; - GetPartURL_Request: { - /** @description Id of upload resource */ - resourceId: string; - /** - * @description Part to be uploaded. It is responsibility of the Client to watch after already uploaded parts: - * - client can request an URL for the same part twice (request -> request) without errors; - * - client can request an URL for alrady uploaded part (request -> upload -> request) without errors. - * - * Parts enumeration starts from 1. - */ - partNumber: string; - /** - * @description Size of the part uploaded by client earlier. Allows controller to count upload progress - * based on client's input. - * Client is free to never sent this value (send zeroes in each request). - */ - uploadedPartSize: string; - /** - * @description Do we need to presign URL for internal use. - * Controllers could use this if they are trying to download something from internal network. - * For backward compatibility, by default pl backend will presign external urls. - */ - isInternalUse: boolean; - /** - * @description Checksum is not used for now, but it is here for case - * where signing checksum header is required. - */ - partChecksum: string; - }; - GetPartURL_Response: { - /** @description URL for chunk upload */ - uploadUrl: string; - /** @description HTTP method to use for chunk upload, say 'PUT' or 'POST'. */ - method: string; - /** - * @description List of headers with their values, MANDATORY to be sent by the client for the upload. - * The destination service (the one, that will handle upload request for specific part) - * may reject the request if it would not keep the given headers. - */ - headers: components["schemas"]["GetPartURL_HTTPHeader"][]; - /** - * @description The number of the _first_ byte in the chunk. - * Absolute position from the start of the file ( file.seek(, SEEK_START) ). - * The client is expected to send [; ) range. - */ - chunkStart: string; - /** - * @description The number of the byte _after_ the last to be sent in the chunk. - * Absolute position from the start of the file. - * The client is expected to send [; ) range. - */ - chunkEnd: string; - }; - /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ - GoogleProtobufAny: { - /** @description The type of the serialized message. */ - "@type": string; - } & { - [key: string]: unknown; - }; - Init_Request: { - /** @description Id of upload resource */ - resourceId: string; - }; - Init_Response: { - /** - * @description Number of parts in this upload. - * For parallel upload support, client can generate any number of part upload URLs - * at the moment and upload them in parallel. - * keeps the number of chunks supported by this upload. - * The parts count is calculated from the planned size of the upload, controller - * configuration and underlying storage restrictions. - */ - partsCount: string; - partSize: string; - /** - * Format: enum - * @description Checksum algorithm to use for the part upload. - */ - checksumAlgorithm: number; - /** @description Header name to use for the checksum. */ - checksumHeader: string; - /** - * @description List of IDs of parts that were already uploaded by client. - * Helps client to recover upload and skip already done parts - * after being interrupted in the middle of the upload - * (say, because of the restart). - * Parts enumeration starts from 1. - */ - uploadedParts: string[]; - }; - /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ - Status: { - /** - * Format: int32 - * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. - */ - code: number; - /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ - message: string; - /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ - details: components["schemas"]["GoogleProtobufAny"][]; - }; - UpdateProgress_Request: { - /** @description Id of upload resource */ - resourceId: string; - /** - * @description Amount of bytes, uploaded since the earlier call to UpdateProgress. - * This value is just blindly added to the 'bytes_processed' of progress report, - * so other clients can see the upload progress. - * If client uploads the data in several streams (several chunks in parallel), it - * can safely send progress updates individually for each of the streams, just counting - * bytes uploaded by particular stream. - * - * Negative value can be used to report about upload retry: when upload was interrupted, - * part of the uploaded data is lost and require re-upload. - */ - bytesProcessed: string; + schemas: { + Finalize_Request: { + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * Format: enum + * @description The client can send the final checksum of the whole file to verify the upload. + * The algorithm used to calculate this final checksum must match the algorithm from + * Init.Response. The server may reject the request on a mismatch + * (meaning the server does not support the given algorithm). + */ + checksumAlgorithm: number; + /** + * Format: bytes + * @description Checksum of the whole file for validation. When provided, the server may verify the upload before + * marking it final. This is optional behavior that depends on the particular storage driver + * used on the backend side for this upload. See the storage driver's implementation for more details. + */ + checksum: string; + }; + Finalize_Response: Record; + GetPartURL_HTTPHeader: { + name: string; + value: string; + }; + GetPartURL_Request: { + /** @description ID of the upload resource */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: + * - client can request a URL for the same part twice (request -> request) without errors; + * - client can request a URL for an already uploaded part (request -> upload -> request) without errors. + * + * Parts enumeration starts from 1. + */ + partNumber: string; + /** + * @description Size of the part uploaded by the client earlier. Allows the controller to count upload progress + * based on the client's input. + * The client is free to never send this value (send zeroes in each request). + */ + uploadedPartSize: string; + /** + * @description Whether to presign the URL for internal use. + * Controllers could use this if they are trying to upload something from the internal network. + * For backward compatibility, by default the pl backend will presign external URLs. + */ + isInternalUse: boolean; + /** + * @description Checksum is not used for now, but it is here for the case + * where signing the checksum header is required. + */ + partChecksum: string; + }; + GetPartURL_Response: { + /** @description URL for chunk upload */ + uploadUrl: string; + /** @description HTTP method to use for chunk upload, say 'PUT' or 'POST'. */ + method: string; + /** + * @description List of headers with their values that MUST be sent by the client for the upload. + * The destination service (the one that will handle the upload request for a specific part) + * may reject the request if it does not include the given headers. + */ + headers: components["schemas"]["GetPartURL_HTTPHeader"][]; + /** + * @description The number of the _first_ byte in the chunk. + * Absolute position from the start of the file ( file.seek(, SEEK_START) ). + * The client is expected to send the [; ) range. + */ + chunkStart: string; + /** + * @description The number of the byte _after_ the last to be sent in the chunk. + * Absolute position from the start of the file. + * The client is expected to send the [; ) range. + */ + chunkEnd: string; + }; + /** @description Contains an arbitrary serialized message along with a @type that describes the type of the serialized message. */ + GoogleProtobufAny: { + /** @description The type of the serialized message. */ + "@type": string; + } & { + [key: string]: unknown; + }; + Init_Request: { + /** @description ID of the upload resource */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + }; + Init_Response: { + /** + * @description Number of parts in this upload. + * For parallel upload support, the client can generate any number of part upload URLs + * at once and upload them in parallel. + * keeps the number of chunks supported by this upload. + * The parts count is calculated from the planned size of the upload, controller + * configuration, and underlying storage restrictions. + */ + partsCount: string; + partSize: string; + /** + * Format: enum + * @description Checksum algorithm to use for each part upload and final verification. + */ + checksumAlgorithm: number; + /** + * @description Header name to use for the checksum in each part upload request. + * A non-empty value combined with checksum_algorithm enables each part upload verification. + */ + checksumHeader: string; + /** + * @description List of IDs of parts that were already uploaded by the client. + * Helps the client recover the upload and skip already uploaded parts + * after being interrupted in the middle of the upload + * (say, because of a restart). + * Parts enumeration starts from 1. + */ + uploadedParts: string[]; + }; + Reset_Request: { + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + }; + Reset_Response: Record; + /** @description The `Status` type defines a logical error model that is suitable for different programming environments, including REST APIs and RPC APIs. It is used by [gRPC](https://github.com/grpc). Each `Status` message contains three pieces of data: error code, error message, and error details. You can find out more about this error model and how to work with it in the [API Design Guide](https://cloud.google.com/apis/design/errors). */ + Status: { + /** + * Format: int32 + * @description The status code, which should be an enum value of [google.rpc.Code][google.rpc.Code]. + */ + code: number; + /** @description A developer-facing error message, which should be in English. Any user-facing error message should be localized and sent in the [google.rpc.Status.details][google.rpc.Status.details] field, or localized by the client. */ + message: string; + /** @description A list of messages that carry the error details. There is a common set of message types for APIs to use. */ + details: components["schemas"]["GoogleProtobufAny"][]; + }; + UpdateProgress_Request: { + /** @description ID of the upload resource */ + resourceId: string; + /** + * Format: bytes + * @description Signature proving the caller is authorized to access this resource. + */ + resourceSignature: string; + /** + * @description Number of bytes uploaded since the earlier call to UpdateProgress. + * This value is just blindly added to the 'bytes_processed' of the progress report, + * so other clients can see the upload progress. + * If the client uploads the data in several streams (several chunks in parallel), it + * can safely send progress updates individually for each of the streams, just counting + * bytes uploaded by a particular stream. + * + * A negative value can be used to report an upload retry: when the upload was interrupted, + * part of the uploaded data is lost and requires a re-upload. + */ + bytesProcessed: string; + }; + UpdateProgress_Response: Record; }; - UpdateProgress_Response: Record; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; } export type $defs = Record; export interface operations { - Upload_Finalize: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Finalize_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; - }; - content: { - "application/json": components["schemas"]["Finalize_Response"]; + Upload_Finalize: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["Finalize_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Finalize_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; - }; - }; - Upload_GetPartURL: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["GetPartURL_Request"]; - }; }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; + Upload_GetPartURL: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - content: { - "application/json": components["schemas"]["GetPartURL_Response"]; + requestBody: { + content: { + "application/json": components["schemas"]["GetPartURL_Request"]; + }; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["GetPartURL_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Upload_Init: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; - }; - requestBody: { - content: { - "application/json": components["schemas"]["Init_Request"]; - }; }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; + Upload_Init: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - content: { - "application/json": components["schemas"]["Init_Response"]; + requestBody: { + content: { + "application/json": components["schemas"]["Init_Request"]; + }; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Init_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - content: { - "application/json": components["schemas"]["Status"]; - }; - }; - }; - }; - Upload_UpdateProgress: { - parameters: { - query?: never; - header?: never; - path?: never; - cookie?: never; }; - requestBody: { - content: { - "application/json": components["schemas"]["UpdateProgress_Request"]; - }; - }; - responses: { - /** @description OK */ - 200: { - headers: { - [name: string]: unknown; + Upload_Reset: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; + }; + requestBody: { + content: { + "application/json": components["schemas"]["Reset_Request"]; + }; }; - content: { - "application/json": components["schemas"]["UpdateProgress_Response"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Reset_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; + }; + }; + Upload_UpdateProgress: { + parameters: { + query?: never; + header?: never; + path?: never; + cookie?: never; }; - }; - /** @description Default error response */ - default: { - headers: { - [name: string]: unknown; + requestBody: { + content: { + "application/json": components["schemas"]["UpdateProgress_Request"]; + }; }; - content: { - "application/json": components["schemas"]["Status"]; + responses: { + /** @description OK */ + 200: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["UpdateProgress_Response"]; + }; + }; + /** @description Default error response */ + default: { + headers: { + [name: string]: unknown; + }; + content: { + "application/json": components["schemas"]["Status"]; + }; + }; }; - }; }; - }; } From de5235365fde35499339dd344b58b9c69ff34940 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Mon, 6 Apr 2026 23:40:11 +0200 Subject: [PATCH 08/27] MILAB-5815: feat: changeset message --- .changeset/cuddly-dancers-greet.md | 100 ++++++++++++++++++++++++----- 1 file changed, 83 insertions(+), 17 deletions(-) diff --git a/.changeset/cuddly-dancers-greet.md b/.changeset/cuddly-dancers-greet.md index 0b0df67130..2702dcc77b 100644 --- a/.changeset/cuddly-dancers-greet.md +++ b/.changeset/cuddly-dancers-greet.md @@ -2,25 +2,91 @@ "@milaboratories/pl-client": major --- -Sync pl proto API: resource signing, locks, access control, field renames. +Sync pl proto API: resource signing, access control, locks, auth, field renames. -**Breaking changes (proto field renames):** +**Breaking changes:** + +Proto field renames: - `Resource.id` → `Resource.resource_id` - `ResourceAPI.Remove.Request.id` → `ResourceAPI.Remove.Request.resource_id` - `FieldAPI.SetError.Request.err_resource_id` → `FieldAPI.SetError.Request.error_resource_id` -- `CacheAPI.DeleteExpiredRecords` deprecated (replaced with `Util.Deprecated`) - -**New proto fields — resource signing (color proof):** -- `Resource.resource_signature` — signature for resource ID -- `Field.value_signature`, `Field.error_signature` — signatures for field references -- `resource_signature` added to most request/response messages (Remove, Get, LockInputs, LockOutputs, Exists, SetError, Tree, TreeSize, FieldList, KV operations, Lease) -- `color_proof` added to resource creation requests (CreateStruct, CreateEphemeral, CreateValue, CreateSingleton, CreateRoot) -- `TxAPI.SetDefaultColor` — new TX operation to set default color for resource creation - -**New API features:** -- `LocksAPI.LockFieldValues` — optimistic locking for resolved field values -- `ResourceSchema.AccessFlags` — per-resource-type access restrictions for non-controller roles (create, read/write fields, read/write KV, per-field-type overrides) + +Proto field removals: +- `MaintenanceAPI.Ping.Response.server_info` removed (field 3) + +Proto deprecations: +- `CacheAPI.DeleteExpiredRecords` replaced with `Util.Deprecated` placeholder + +Lease endpoint URL changes: +- `/v1/locks/lease` → `/v1/locks/lease/create` +- `PUT /v1/locks/lease` → `POST /v1/locks/lease/update` +- `DELETE /v1/locks/lease` → `POST /v1/locks/lease/release` + +**New proto fields — resource signing:** + +Core messages: +- `Resource.resource_signature` (bytes) — opaque signature for resource ID +- `Field.value_signature` (bytes) — signature for field value resource, inheriting parent color +- `Field.error_signature` (bytes) — signature for error resource, inheriting parent color +- `FieldRef.resource_signature` (bytes) — signature for the referenced resource + +Transaction operations: +- `TxAPI.SetDefaultColor` — set default color for resource creation via `color_proof` + +Resource creation — `color_proof` added to: +- `CreateStruct.Request`, `CreateEphemeral.Request`, `CreateValue.Request`, `CreateSingleton.Request` + +Resource creation — `resource_signature` added to responses: +- `CreateStruct`, `CreateEphemeral`, `CreateValue`, `CreateSingleton`, `CreateRoot`, `GetValueID` + +Resource access — `resource_signature` added to requests: +- `Remove`, `Get`, `LockInputs`, `LockOutputs`, `Exists`, `SetError` (+ `error_resource_signature`), `Tree`, `TreeSize`, `Name.Set` + +Resource access — `resource_signature` added to responses: +- `Name.Get` + +Field operations — `resource_signature` added to: +- `FieldAPI.List.Request`, `FieldAPI.SetError.Request` (`error_resource_signature`) + +KV operations — `resource_signature` added to all `ResourceKVAPI.*.Request`: +- `Set`, `Get`, `GetIfExists`, `Delete`, `SetFlag`, `GetFlag`, `List` + +Lease operations — `resource_signature` added to: +- `Lease.Create.Request`, `Lease.Update.Request`, `Lease.Release.Request` + +**New API — access control:** + +RPCs: +- `GrantAccess` — grant resource access to another user +- `RevokeGrant` — revoke previously granted access +- `ListGrants` — server-side streaming, list grants for a resource +- `ListUserResources` — server-side streaming, user root + shared resources with pagination + +Messages: +- `AuthAPI.Grant` — grant record (user, resource_id, permissions, granted_by, granted_at) +- `AuthAPI.Grant.Permissions` — access bitmask (writable) +- `AuthAPI.ListUserResources.UserRoot` — signed user root +- `AuthAPI.ListUserResources.SharedResource` — signed shared resource with type and permissions + +**New API — auth:** + +- `AuthAPI.GetJWTToken.Role` enum — `ROLE_UNSPECIFIED`, `USER`, `CONTROLLER`, `WORKFLOW` +- `AuthAPI.GetJWTToken.Request.requested_role` — request JWT with specific role +- `AuthAPI.GetJWTToken.Response.session_id` — 128-bit session ID + +**New API — locks:** + +- `LocksAPI.LockFieldValues` — optimistic locking on resolved field values + +**New API — schema:** + +- `ResourceSchema.AccessFlags` — per-type access restrictions for non-controller roles (create_resource, read_fields, write_fields, read_kv, write_kv, per-field-type overrides via read_by_field_type/write_by_field_type maps) - `ResourceSchema.free_inputs` / `free_outputs` — skip automatic locking on creation -- `Notification.Events.resource_recovered` — new notification event -- `AuthAPI.GetJWTToken.Role` — request JWT with specific role -- `AuthAPI.GetJWTToken.Response.session_id` — session ID in JWT response + +**New API — notifications:** + +- `Notification.Events.resource_recovered` — new event type + +**New utility:** + +- `Util.Deprecated` — empty message for deprecated oneOf slots From 16bdf1e13100fff635886b5a03a1710488b574a3 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Mon, 6 Apr 2026 23:46:28 +0200 Subject: [PATCH 09/27] MILAB-5815: feat: proto optional signature (temporary) --- .../proto/plapi/plapiproto/api.proto | 78 +++++++++---------- .../proto/plapi/plapiproto/api_types.proto | 6 +- .../proto/plapi/plapiproto/base_types.proto | 2 +- .../proto/shared/downloadapi/protocol.proto | 2 +- .../proto/shared/lsapi/protocol.proto | 2 +- .../proto/shared/progressapi/protocol.proto | 4 +- .../proto/shared/streamingapi/protocol.proto | 6 +- .../proto/shared/uploadapi/protocol.proto | 10 +-- .../shared/grpc/downloadapi/protocol.ts | 13 ++-- 9 files changed, 61 insertions(+), 62 deletions(-) diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api.proto b/lib/node/pl-client/proto/plapi/plapiproto/api.proto index 6dee23ae8f..78d13e5927 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api.proto @@ -584,7 +584,7 @@ message TxAPI { message SetDefaultColor { message Request { - bytes color_proof = 1; + optional bytes color_proof = 1; } message Response {} } @@ -597,12 +597,12 @@ message ResourceAPI { Base.ResourceType type = 3; optional bytes data = 4; - bytes color_proof = 5; + optional bytes color_proof = 5; } message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } message CreateEphemeral { @@ -611,12 +611,12 @@ message ResourceAPI { Base.ResourceType type = 3; optional bytes data = 4; - bytes color_proof = 5; + optional bytes color_proof = 5; } message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -640,12 +640,12 @@ message ResourceAPI { bytes data = 6; bool error_if_exists = 7; - bytes color_proof = 8; + optional bytes color_proof = 8; } message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -657,7 +657,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -670,12 +670,12 @@ message ResourceAPI { bool error_if_exists = 7; - bytes color_proof = 8; + optional bytes color_proof = 8; } message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -695,7 +695,7 @@ message ResourceAPI { message LockInputs { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response {} @@ -704,7 +704,7 @@ message ResourceAPI { message LockOutputs { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response {} @@ -713,7 +713,7 @@ message ResourceAPI { message Exists { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response { @@ -724,10 +724,10 @@ message ResourceAPI { message SetError { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; uint64 error_resource_id = 2; - bytes error_resource_signature = 4; + optional bytes error_resource_signature = 4; } message Response {} } @@ -735,7 +735,7 @@ message ResourceAPI { message Get { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; bool load_fields = 2; } @@ -781,7 +781,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -791,7 +791,7 @@ message ResourceAPI { message Remove { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response {} @@ -801,7 +801,7 @@ message ResourceAPI { message Set { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; string name = 2; } @@ -815,7 +815,7 @@ message ResourceAPI { message Response { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } } @@ -841,7 +841,7 @@ message ResourceAPI { message Tree { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; // Limit the maximum depth the tree is traversed. // The resource is considered at depth = 0, the values of its fields @@ -861,7 +861,7 @@ message ResourceAPI { message TreeSize { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response { // size of all tree resources in bytes @@ -909,7 +909,7 @@ message FieldAPI { message Request { Base.FieldRef field = 1; uint64 error_resource_id = 2; - bytes error_resource_signature = 3; + optional bytes error_resource_signature = 3; } message Response {} @@ -942,7 +942,7 @@ message FieldAPI { message List { message Request { uint64 resource_id = 1; - bytes resource_signature = 4; + optional bytes resource_signature = 4; // Start the listing from given position, returning first field with // name >= start_from. @@ -1196,7 +1196,7 @@ message ResourceKVAPI { message List { message Request { uint64 resource_id = 1; - bytes resource_signature = 4; + optional bytes resource_signature = 4; // Start the listing from given position, returning first item with // key >= start_from. @@ -1230,7 +1230,7 @@ message ResourceKVAPI { message Set { message Request { uint64 resource_id = 1; - bytes resource_signature = 4; + optional bytes resource_signature = 4; string key = 2; bytes value = 3; @@ -1242,7 +1242,7 @@ message ResourceKVAPI { message Get { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; string key = 2; } @@ -1255,7 +1255,7 @@ message ResourceKVAPI { message GetIfExists { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; string key = 2; } @@ -1269,7 +1269,7 @@ message ResourceKVAPI { message Delete { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; string key = 2; } @@ -1280,7 +1280,7 @@ message ResourceKVAPI { message SetFlag { message Request { uint64 resource_id = 1; - bytes resource_signature = 4; + optional bytes resource_signature = 4; string key = 2; bool value = 3; @@ -1292,7 +1292,7 @@ message ResourceKVAPI { message GetFlag { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; string key = 2; } @@ -1418,7 +1418,7 @@ message LocksAPI { message Create { message Request { uint64 resource_id = 1; - bytes resource_signature = 5; + optional bytes resource_signature = 5; google.protobuf.Duration timeout = 3; string name = 4; @@ -1431,7 +1431,7 @@ message LocksAPI { message Update { message Request { uint64 resource_id = 1; - bytes resource_signature = 5; + optional bytes resource_signature = 5; bytes lease_id = 2; google.protobuf.Duration timeout = 3; @@ -1443,7 +1443,7 @@ message LocksAPI { message Release { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; bytes lease_id = 2; } @@ -1489,7 +1489,7 @@ message AuthAPI { message GrantAccess { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; string target_user = 3; // user login to grant access to Grant.Permissions permissions = 4; // access level to grant } @@ -1500,7 +1500,7 @@ message AuthAPI { message RevokeGrant { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; string target_user = 3; // user login whose grant to revoke } @@ -1510,7 +1510,7 @@ message AuthAPI { message ListGrants { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response { @@ -1562,12 +1562,12 @@ message AuthAPI { message UserRoot { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message SharedResource { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; Base.ResourceType resource_type = 3; Grant.Permissions permissions = 4; } diff --git a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto index 80a10b7678..74eeeff1b5 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/api_types.proto @@ -21,7 +21,7 @@ message Resource { } uint64 resource_id = 2; - bytes resource_signature = 18; + optional bytes resource_signature = 18; bytes canonical_id = 17; // could be empty; it depends on the resource lifecycle state Kind kind = 3; Base.ResourceType type = 4; @@ -65,7 +65,7 @@ message Field { uint64 value = 5; // Signature for value resource ID, inheriting the parent resource's color. // Populated server-side when the parent resource has a known color in the current TX. - bytes value_signature = 10; + optional bytes value_signature = 10; enum ValueStatus { INVALID = 0; @@ -84,7 +84,7 @@ message Field { // Is intended to report problems _from_ the platform to the client. uint64 error = 6; // Signature for error resource ID, inheriting the parent resource's color. - bytes error_signature = 11; + optional bytes error_signature = 11; } message Notification { diff --git a/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto b/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto index d34210c432..b414ee8bc5 100644 --- a/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto +++ b/lib/node/pl-client/proto/plapi/plapiproto/base_types.proto @@ -23,6 +23,6 @@ enum FieldType { message FieldRef { uint64 resource_id = 2; - bytes resource_signature = 4; + optional bytes resource_signature = 4; string field_name = 3; } diff --git a/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto b/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto index 0493740d67..e71546e625 100644 --- a/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/downloadapi/protocol.proto @@ -23,7 +23,7 @@ message DownloadAPI { message Request { uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 3; + optional bytes resource_signature = 3; // Pass `true` here if the blob will be downloaded from the internal network, // e.g. controllers could use this if they are trying to download something from the internal network. diff --git a/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto b/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto index 2d9d2b7225..cb08985812 100644 --- a/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/lsapi/protocol.proto @@ -61,7 +61,7 @@ message LsAPI { // resource_id of 'LS/' resource uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 3; + optional bytes resource_signature = 3; // Location to list, relative to the storage root. Only items that have starting // with are included in the list response. diff --git a/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto b/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto index 1c8b0ec62f..3a69356014 100644 --- a/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/progressapi/protocol.proto @@ -40,7 +40,7 @@ message ProgressAPI { message GetStatus { message Request { uint64 resource_id = 1; - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response { @@ -51,7 +51,7 @@ message ProgressAPI { message RealtimeStatus { message Request { uint64 resource_id = 1; - bytes resource_signature = 3; + optional bytes resource_signature = 3; google.protobuf.Duration update_interval = 2; } diff --git a/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto b/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto index 499d818f4b..29d325750f 100644 --- a/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/streamingapi/protocol.proto @@ -54,7 +54,7 @@ message StreamingAPI { // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 3; + optional bytes resource_signature = 3; // makes the streamer perform a seek operation to the given offset before sending the data. int64 offset = 2; @@ -70,7 +70,7 @@ message StreamingAPI { // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 3; + optional bytes resource_signature = 3; // makes the streamer perform a seek operation to the given offset before sending the contents. // This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. @@ -102,7 +102,7 @@ message StreamingAPI { // of Stream resource that keeps info on item to be streamed. uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 4; + optional bytes resource_signature = 4; // makes the streamer perform a seek operation to the given offset before sending the contents. // This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. diff --git a/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto b/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto index b2fe9ab5ff..046adeb25b 100644 --- a/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto +++ b/lib/node/pl-drivers/proto/shared/uploadapi/protocol.proto @@ -78,7 +78,7 @@ message UploadAPI { // ID of the upload resource uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response { @@ -112,7 +112,7 @@ message UploadAPI { // ID of the upload resource uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 3; + optional bytes resource_signature = 3; // Number of bytes uploaded since the earlier call to UpdateProgress. // This value is just blindly added to the 'bytes_processed' of the progress report, @@ -134,7 +134,7 @@ message UploadAPI { // ID of the upload resource uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 6; + optional bytes resource_signature = 6; // Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: // - client can request a URL for the same part twice (request -> request) without errors; @@ -191,7 +191,7 @@ message UploadAPI { message Request { uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 4; + optional bytes resource_signature = 4; // The client can send the final checksum of the whole file to verify the upload. // The algorithm used to calculate this final checksum must match the algorithm from @@ -212,7 +212,7 @@ message UploadAPI { message Request { uint64 resource_id = 1; // Signature proving the caller is authorized to access this resource. - bytes resource_signature = 2; + optional bytes resource_signature = 2; } message Response {} diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts index cc22ee7077..633c42ae34 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/downloadapi/protocol.ts @@ -32,9 +32,9 @@ export interface DownloadAPI_GetDownloadURL_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Pass `true` here if the blob will be downloaded from the internal network, * e.g. controllers could use this if they are trying to download something from the internal network. @@ -151,14 +151,13 @@ class DownloadAPI_GetDownloadURL_Request$Type extends MessageType): DownloadAPI_GetDownloadURL_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.isInternalUse = false; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -172,7 +171,7 @@ class DownloadAPI_GetDownloadURL_Request$Type extends MessageType Date: Mon, 6 Apr 2026 23:48:32 +0200 Subject: [PATCH 10/27] MILAB-5815: autogen: regenerate --- .../milaboratory/pl/plapi/plapiproto/api.ts | 507 ++++++++---------- .../pl/plapi/plapiproto/api_types.ts | 39 +- .../pl/plapi/plapiproto/base_types.ts | 13 +- .../controllers/shared/grpc/lsapi/protocol.ts | 13 +- .../shared/grpc/progressapi/protocol.ts | 26 +- .../shared/grpc/streamingapi/protocol.ts | 39 +- .../shared/grpc/uploadapi/protocol.ts | 65 ++- 7 files changed, 324 insertions(+), 378 deletions(-) diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts index 7cf40b9317..ebbbc55504 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/api.ts @@ -1067,9 +1067,9 @@ export interface TxAPI_SetDefaultColor { */ export interface TxAPI_SetDefaultColor_Request { /** - * @generated from protobuf field: bytes color_proof = 1 + * @generated from protobuf field: optional bytes color_proof = 1 */ - colorProof: Uint8Array; + colorProof?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.TxAPI.SetDefaultColor.Response @@ -1103,9 +1103,9 @@ export interface ResourceAPI_CreateStruct_Request { */ data?: Uint8Array; /** - * @generated from protobuf field: bytes color_proof = 5 + * @generated from protobuf field: optional bytes color_proof = 5 */ - colorProof: Uint8Array; + colorProof?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateStruct.Response @@ -1116,9 +1116,9 @@ export interface ResourceAPI_CreateStruct_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateEphemeral @@ -1142,9 +1142,9 @@ export interface ResourceAPI_CreateEphemeral_Request { */ data?: Uint8Array; /** - * @generated from protobuf field: bytes color_proof = 5 + * @generated from protobuf field: optional bytes color_proof = 5 */ - colorProof: Uint8Array; + colorProof?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateEphemeral.Response @@ -1155,9 +1155,9 @@ export interface ResourceAPI_CreateEphemeral_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateChild @@ -1216,9 +1216,9 @@ export interface ResourceAPI_CreateValue_Request { */ errorIfExists: boolean; /** - * @generated from protobuf field: bytes color_proof = 8 + * @generated from protobuf field: optional bytes color_proof = 8 */ - colorProof: Uint8Array; + colorProof?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateValue.Response @@ -1229,9 +1229,9 @@ export interface ResourceAPI_CreateValue_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.GetValueID @@ -1260,9 +1260,9 @@ export interface ResourceAPI_GetValueID_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateSingleton @@ -1290,9 +1290,9 @@ export interface ResourceAPI_CreateSingleton_Request { */ errorIfExists: boolean; /** - * @generated from protobuf field: bytes color_proof = 8 + * @generated from protobuf field: optional bytes color_proof = 8 */ - colorProof: Uint8Array; + colorProof?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.CreateSingleton.Response @@ -1303,9 +1303,9 @@ export interface ResourceAPI_CreateSingleton_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.GetSingleton @@ -1352,9 +1352,9 @@ export interface ResourceAPI_LockInputs_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.LockInputs.Response @@ -1375,9 +1375,9 @@ export interface ResourceAPI_LockOutputs_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.LockOutputs.Response @@ -1398,9 +1398,9 @@ export interface ResourceAPI_Exists_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Exists.Response @@ -1425,17 +1425,17 @@ export interface ResourceAPI_SetError_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: uint64 error_resource_id = 2 */ errorResourceId: bigint; /** - * @generated from protobuf field: bytes error_resource_signature = 4 + * @generated from protobuf field: optional bytes error_resource_signature = 4 */ - errorResourceSignature: Uint8Array; + errorResourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.SetError.Response @@ -1456,9 +1456,9 @@ export interface ResourceAPI_Get_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: bool load_fields = 2 */ @@ -1557,9 +1557,9 @@ export interface ResourceAPI_CreateRoot_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } // FIXME: add CreateResource method to API @@ -1579,9 +1579,9 @@ export interface ResourceAPI_Remove_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Remove.Response @@ -1607,9 +1607,9 @@ export interface ResourceAPI_Name_Set_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string name = 2 */ @@ -1643,9 +1643,9 @@ export interface ResourceAPI_Name_Get_Response { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.Name.Exists @@ -1703,9 +1703,9 @@ export interface ResourceAPI_Tree_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Limit the maximum depth the tree is traversed. * The resource is considered at depth = 0, the values of its fields @@ -1743,9 +1743,9 @@ export interface ResourceAPI_TreeSize_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.ResourceAPI.TreeSize.Response @@ -1861,9 +1861,9 @@ export interface FieldAPI_SetError_Request { */ errorResourceId: bigint; /** - * @generated from protobuf field: bytes error_resource_signature = 3 + * @generated from protobuf field: optional bytes error_resource_signature = 3 */ - errorResourceSignature: Uint8Array; + errorResourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.FieldAPI.SetError.Response @@ -1945,9 +1945,9 @@ export interface FieldAPI_List_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Start the listing from given position, returning first field with * name >= start_from. @@ -2560,9 +2560,9 @@ export interface ResourceKVAPI_List_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Start the listing from given position, returning first item with * key >= start_from. @@ -2628,9 +2628,9 @@ export interface ResourceKVAPI_Set_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2659,9 +2659,9 @@ export interface ResourceKVAPI_Get_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2690,9 +2690,9 @@ export interface ResourceKVAPI_GetIfExists_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2725,9 +2725,9 @@ export interface ResourceKVAPI_Delete_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2752,9 +2752,9 @@ export interface ResourceKVAPI_SetFlag_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -2783,9 +2783,9 @@ export interface ResourceKVAPI_GetFlag_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string key = 2 */ @@ -3103,9 +3103,9 @@ export interface LocksAPI_Lease_Create_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 5 + * @generated from protobuf field: optional bytes resource_signature = 5 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: google.protobuf.Duration timeout = 3 */ @@ -3138,9 +3138,9 @@ export interface LocksAPI_Lease_Update_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 5 + * @generated from protobuf field: optional bytes resource_signature = 5 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: bytes lease_id = 2 */ @@ -3173,9 +3173,9 @@ export interface LocksAPI_Lease_Release_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: bytes lease_id = 2 */ @@ -3297,9 +3297,9 @@ export interface AuthAPI_GrantAccess_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string target_user = 3 */ @@ -3328,9 +3328,9 @@ export interface AuthAPI_RevokeGrant_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string target_user = 3 */ @@ -3355,9 +3355,9 @@ export interface AuthAPI_ListGrants_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListGrants.Response @@ -3478,9 +3478,9 @@ export interface AuthAPI_ListUserResources_UserRoot { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.PL.API.AuthAPI.ListUserResources.SharedResource @@ -3491,9 +3491,9 @@ export interface AuthAPI_ListUserResources_SharedResource { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: MiLaboratories.PL.Base.ResourceType resource_type = 3 */ @@ -5688,12 +5688,11 @@ export const TxAPI_SetDefaultColor = new TxAPI_SetDefaultColor$Type(); class TxAPI_SetDefaultColor_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.TxAPI.SetDefaultColor.Request", [ - { no: 1, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 1, name: "color_proof", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): TxAPI_SetDefaultColor_Request { const message = globalThis.Object.create((this.messagePrototype!)); - message.colorProof = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5703,7 +5702,7 @@ class TxAPI_SetDefaultColor_Request$Type extends MessageType ResourceType }, { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, - { no: 5, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 5, name: "color_proof", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateStruct_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.id = 0n; - message.colorProof = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5877,7 +5875,7 @@ class ResourceAPI_CreateStruct_Request$Type extends MessageType): ResourceAPI_CreateStruct_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -5938,7 +5935,7 @@ class ResourceAPI_CreateStruct_Response$Type extends MessageType ResourceType }, { no: 4, name: "data", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, - { no: 5, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 5, name: "color_proof", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateEphemeral_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.id = 0n; - message.colorProof = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6039,7 +6035,7 @@ class ResourceAPI_CreateEphemeral_Request$Type extends MessageType): ResourceAPI_CreateEphemeral_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6100,7 +6095,7 @@ class ResourceAPI_CreateEphemeral_Response$Type extends MessageType ResourceType }, { no: 6, name: "data", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, - { no: 8, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 8, name: "color_proof", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateValue_Request { @@ -6331,7 +6326,6 @@ class ResourceAPI_CreateValue_Request$Type extends MessageType(this, message, value); return message; @@ -6353,7 +6347,7 @@ class ResourceAPI_CreateValue_Request$Type extends MessageType): ResourceAPI_CreateValue_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6417,7 +6410,7 @@ class ResourceAPI_CreateValue_Response$Type extends MessageType): ResourceAPI_GetValueID_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6564,7 +6556,7 @@ class ResourceAPI_GetValueID_Response$Type extends MessageType ResourceType }, { no: 6, name: "data", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 7, name: "error_if_exists", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, - { no: 8, name: "color_proof", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 8, name: "color_proof", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): ResourceAPI_CreateSingleton_Request { @@ -6649,7 +6641,6 @@ class ResourceAPI_CreateSingleton_Request$Type extends MessageType(this, message, value); return message; @@ -6671,7 +6662,7 @@ class ResourceAPI_CreateSingleton_Request$Type extends MessageType): ResourceAPI_CreateSingleton_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6735,7 +6725,7 @@ class ResourceAPI_CreateSingleton_Response$Type extends MessageType): ResourceAPI_LockInputs_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -6974,7 +6963,7 @@ class ResourceAPI_LockInputs_Request$Type extends MessageType): ResourceAPI_LockOutputs_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7105,7 +7093,7 @@ class ResourceAPI_LockOutputs_Request$Type extends MessageType): ResourceAPI_Exists_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7236,7 +7223,7 @@ class ResourceAPI_Exists_Request$Type extends MessageType): ResourceAPI_SetError_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.errorResourceId = 0n; - message.errorResourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7380,13 +7365,13 @@ class ResourceAPI_SetError_Request$Type extends MessageType constructor() { super("MiLaboratories.PL.API.ResourceAPI.Get.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "load_fields", kind: "scalar", T: 8 /*ScalarType.BOOL*/ } ]); } create(value?: PartialMessage): ResourceAPI_Get_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.loadFields = false; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -7525,7 +7509,7 @@ class ResourceAPI_Get_Request$Type extends MessageType case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 3: + case /* optional bytes resource_signature */ 3: message.resourceSignature = reader.bytes(); break; case /* bool load_fields */ 2: @@ -7549,8 +7533,8 @@ class ResourceAPI_Get_Request$Type extends MessageType /* bool load_fields = 2; */ if (message.loadFields !== false) writer.tag(2, WireType.Varint).bool(message.loadFields); - /* bytes resource_signature = 3; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 3; */ + if (message.resourceSignature !== undefined) writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) @@ -7905,13 +7889,12 @@ class ResourceAPI_CreateRoot_Response$Type extends MessageType): ResourceAPI_CreateRoot_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -7924,7 +7907,7 @@ class ResourceAPI_CreateRoot_Response$Type extends MessageType): ResourceAPI_Remove_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8017,7 +7999,7 @@ class ResourceAPI_Remove_Request$Type extends MessageType): ResourceAPI_Name_Set_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.name = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -8188,7 +8169,7 @@ class ResourceAPI_Name_Set_Request$Type extends MessageType): ResourceAPI_Name_Get_Response { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8372,7 +8352,7 @@ class ResourceAPI_Name_Get_Response$Type extends MessageType): ResourceAPI_Tree_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8721,7 +8700,7 @@ class ResourceAPI_Tree_Request$Type extends MessageType): ResourceAPI_TreeSize_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -8866,7 +8844,7 @@ class ResourceAPI_TreeSize_Request$Type extends MessageType FieldRef }, { no: 2, name: "error_resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 3, name: "error_resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 3, name: "error_resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): FieldAPI_SetError_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.errorResourceId = 0n; - message.errorResourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -9454,7 +9431,7 @@ class FieldAPI_SetError_Request$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.FieldAPI.List.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 4, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "start_from", kind: "scalar", T: 9 /*ScalarType.STRING*/ }, { no: 3, name: "limit", kind: "scalar", T: 13 /*ScalarType.UINT32*/ } ]); @@ -9951,7 +9928,6 @@ class FieldAPI_List_Request$Type extends MessageType { create(value?: PartialMessage): FieldAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.startFrom = ""; message.limit = 0; if (value !== undefined) @@ -9966,7 +9942,7 @@ class FieldAPI_List_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 4: + case /* optional bytes resource_signature */ 4: message.resourceSignature = reader.bytes(); break; case /* string start_from */ 2: @@ -9996,8 +9972,8 @@ class FieldAPI_List_Request$Type extends MessageType { /* uint32 limit = 3; */ if (message.limit !== 0) writer.tag(3, WireType.Varint).uint32(message.limit); - /* bytes resource_signature = 4; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 4; */ + if (message.resourceSignature !== undefined) writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) @@ -13190,7 +13166,7 @@ class ResourceKVAPI_List_Request$Type extends MessageType): ResourceKVAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.startFrom = ""; message.limit = 0; if (value !== undefined) @@ -13213,7 +13188,7 @@ class ResourceKVAPI_List_Request$Type extends MessageType): ResourceKVAPI_Set_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; message.value = new Uint8Array(0); if (value !== undefined) @@ -13431,7 +13405,7 @@ class ResourceKVAPI_Set_Request$Type extends MessageType): ResourceKVAPI_Get_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -13576,7 +13549,7 @@ class ResourceKVAPI_Get_Request$Type extends MessageType): ResourceKVAPI_GetIfExists_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -13724,7 +13696,7 @@ class ResourceKVAPI_GetIfExists_Request$Type extends MessageType): ResourceKVAPI_Delete_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -13880,7 +13851,7 @@ class ResourceKVAPI_Delete_Request$Type extends MessageType): ResourceKVAPI_SetFlag_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; message.value = false; if (value !== undefined) @@ -14021,7 +13991,7 @@ class ResourceKVAPI_SetFlag_Request$Type extends MessageType): ResourceKVAPI_GetFlag_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.key = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -14166,7 +14135,7 @@ class ResourceKVAPI_GetFlag_Request$Type extends MessageType Duration }, { no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); @@ -15716,7 +15685,6 @@ class LocksAPI_Lease_Create_Request$Type extends MessageType): LocksAPI_Lease_Create_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.name = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -15730,7 +15698,7 @@ class LocksAPI_Lease_Create_Request$Type extends MessageType Duration }, { no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } @@ -15872,7 +15840,6 @@ class LocksAPI_Lease_Update_Request$Type extends MessageType): LocksAPI_Lease_Update_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.leaseId = new Uint8Array(0); message.name = ""; if (value !== undefined) @@ -15887,7 +15854,7 @@ class LocksAPI_Lease_Update_Request$Type extends MessageType): LocksAPI_Lease_Release_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.leaseId = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); @@ -16038,7 +16004,7 @@ class LocksAPI_Lease_Release_Request$Type extends MessageType AuthAPI_Grant_Permissions } ]); @@ -16550,7 +16516,6 @@ class AuthAPI_GrantAccess_Request$Type extends MessageType): AuthAPI_GrantAccess_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.targetUser = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -16564,7 +16529,7 @@ class AuthAPI_GrantAccess_Request$Type extends MessageType): AuthAPI_RevokeGrant_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.targetUser = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -16709,7 +16673,7 @@ class AuthAPI_RevokeGrant_Request$Type extends MessageType): AuthAPI_ListGrants_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -16846,7 +16809,7 @@ class AuthAPI_ListGrants_Request$Type extends MessageType): AuthAPI_ListUserResources_UserRoot { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -17241,7 +17203,7 @@ class AuthAPI_ListUserResources_UserRoot$Type extends MessageType ResourceType }, { no: 4, name: "permissions", kind: "message", T: () => AuthAPI_Grant_Permissions } ]); @@ -17285,7 +17247,6 @@ class AuthAPI_ListUserResources_SharedResource$Type extends MessageType): AuthAPI_ListUserResources_SharedResource { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -17298,7 +17259,7 @@ class AuthAPI_ListUserResources_SharedResource$Type extends MessageType { constructor() { super("MiLaboratories.PL.API.Resource", [ { no: 2, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 18, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 18, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 17, name: "canonical_id", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, { no: 3, name: "kind", kind: "enum", T: () => ["MiLaboratories.PL.API.Resource.Kind", Resource_Kind, "KIND_"] }, { no: 4, name: "type", kind: "message", T: () => ResourceType }, @@ -570,7 +570,6 @@ class Resource$Type extends MessageType { create(value?: PartialMessage): Resource { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.canonicalId = new Uint8Array(0); message.kind = 0; message.data = new Uint8Array(0); @@ -594,7 +593,7 @@ class Resource$Type extends MessageType { case /* uint64 resource_id */ 2: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 18: + case /* optional bytes resource_signature */ 18: message.resourceSignature = reader.bytes(); break; case /* bytes canonical_id */ 17: @@ -702,8 +701,8 @@ class Resource$Type extends MessageType { /* bytes canonical_id = 17; */ if (message.canonicalId.length) writer.tag(17, WireType.LengthDelimited).bytes(message.canonicalId); - /* bytes resource_signature = 18; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 18; */ + if (message.resourceSignature !== undefined) writer.tag(18, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) @@ -770,22 +769,20 @@ class Field$Type extends MessageType { { no: 2, name: "type", kind: "enum", T: () => ["MiLaboratories.PL.Base.FieldType", FieldType] }, { no: 3, name: "features", kind: "message", T: () => Resource_Features }, { no: 5, name: "value", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 10, name: "value_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 10, name: "value_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 7, name: "value_status", kind: "enum", T: () => ["MiLaboratories.PL.API.Field.ValueStatus", Field_ValueStatus] }, { no: 8, name: "value_is_final", kind: "scalar", T: 8 /*ScalarType.BOOL*/ }, { no: 6, name: "error", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 11, name: "error_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 11, name: "error_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): Field { const message = globalThis.Object.create((this.messagePrototype!)); message.type = 0; message.value = 0n; - message.valueSignature = new Uint8Array(0); message.valueStatus = 0; message.valueIsFinal = false; message.error = 0n; - message.errorSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -807,7 +804,7 @@ class Field$Type extends MessageType { case /* uint64 value */ 5: message.value = reader.uint64().toBigInt(); break; - case /* bytes value_signature */ 10: + case /* optional bytes value_signature */ 10: message.valueSignature = reader.bytes(); break; case /* MiLaboratories.PL.API.Field.ValueStatus value_status */ 7: @@ -819,7 +816,7 @@ class Field$Type extends MessageType { case /* uint64 error */ 6: message.error = reader.uint64().toBigInt(); break; - case /* bytes error_signature */ 11: + case /* optional bytes error_signature */ 11: message.errorSignature = reader.bytes(); break; default: @@ -855,11 +852,11 @@ class Field$Type extends MessageType { /* bool value_is_final = 8; */ if (message.valueIsFinal !== false) writer.tag(8, WireType.Varint).bool(message.valueIsFinal); - /* bytes value_signature = 10; */ - if (message.valueSignature.length) + /* optional bytes value_signature = 10; */ + if (message.valueSignature !== undefined) writer.tag(10, WireType.LengthDelimited).bytes(message.valueSignature); - /* bytes error_signature = 11; */ - if (message.errorSignature.length) + /* optional bytes error_signature = 11; */ + if (message.errorSignature !== undefined) writer.tag(11, WireType.LengthDelimited).bytes(message.errorSignature); let u = options.writeUnknownFields; if (u !== false) diff --git a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts index a4b07b6019..0d31c5b927 100644 --- a/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts +++ b/lib/node/pl-client/src/proto-grpc/github.com/milaboratory/pl/plapi/plapiproto/base_types.ts @@ -32,9 +32,9 @@ export interface FieldRef { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: string field_name = 3 */ @@ -133,14 +133,13 @@ class FieldRef$Type extends MessageType { constructor() { super("MiLaboratories.PL.Base.FieldRef", [ { no: 2, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 4, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 3, name: "field_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): FieldRef { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.fieldName = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -154,7 +153,7 @@ class FieldRef$Type extends MessageType { case /* uint64 resource_id */ 2: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 4: + case /* optional bytes resource_signature */ 4: message.resourceSignature = reader.bytes(); break; case /* string field_name */ 3: @@ -178,8 +177,8 @@ class FieldRef$Type extends MessageType { /* string field_name = 3; */ if (message.fieldName !== "") writer.tag(3, WireType.LengthDelimited).string(message.fieldName); - /* bytes resource_signature = 4; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 4; */ + if (message.resourceSignature !== undefined) writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts index 7788c60c20..8e6abd38f2 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts @@ -92,9 +92,9 @@ export interface LsAPI_List_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Location to list, relative to the storage root. Only items that have starting * with are included in the list response. @@ -299,14 +299,13 @@ class LsAPI_List_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.LsAPI.List.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "location", kind: "scalar", T: 9 /*ScalarType.STRING*/ } ]); } create(value?: PartialMessage): LsAPI_List_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.location = ""; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -320,7 +319,7 @@ class LsAPI_List_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 3: + case /* optional bytes resource_signature */ 3: message.resourceSignature = reader.bytes(); break; case /* string location */ 2: @@ -344,8 +343,8 @@ class LsAPI_List_Request$Type extends MessageType { /* string location = 2; */ if (message.location !== "") writer.tag(2, WireType.LengthDelimited).string(message.location); - /* bytes resource_signature = 3; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 3; */ + if (message.resourceSignature !== undefined) writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts index 1a37f63dce..b43872f532 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/progressapi/protocol.ts @@ -58,9 +58,9 @@ export interface ProgressAPI_GetStatus_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.ProgressAPI.GetStatus.Response @@ -85,9 +85,9 @@ export interface ProgressAPI_RealtimeStatus_Request { */ resourceId: bigint; /** - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * @generated from protobuf field: google.protobuf.Duration update_interval = 2 */ @@ -262,13 +262,12 @@ class ProgressAPI_GetStatus_Request$Type extends MessageType): ProgressAPI_GetStatus_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -281,7 +280,7 @@ class ProgressAPI_GetStatus_Request$Type extends MessageType Duration } ]); } create(value?: PartialMessage): ProgressAPI_RealtimeStatus_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -421,7 +419,7 @@ class ProgressAPI_RealtimeStatus_Request$Type extends MessageType makes the streamer perform a seek operation to the given offset before sending the data. * @@ -61,9 +61,9 @@ export interface StreamingAPI_ReadText { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * makes the streamer perform a seek operation to the given offset before sending the contents. * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. @@ -115,9 +115,9 @@ export interface StreamingAPI_LastLines { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * makes the streamer perform a seek operation to the given offset before sending the contents. * This offset is taken in BYTES, as it eases streaming recovery after a client reconnection or controller restart. @@ -226,7 +226,7 @@ class StreamingAPI_ReadBinary$Type extends MessageType constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.ReadBinary", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 11, name: "chunk_size", kind: "scalar", opt: true, T: 13 /*ScalarType.UINT32*/ } ]); @@ -234,7 +234,6 @@ class StreamingAPI_ReadBinary$Type extends MessageType create(value?: PartialMessage): StreamingAPI_ReadBinary { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.offset = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -248,7 +247,7 @@ class StreamingAPI_ReadBinary$Type extends MessageType case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 3: + case /* optional bytes resource_signature */ 3: message.resourceSignature = reader.bytes(); break; case /* int64 offset */ 2: @@ -275,8 +274,8 @@ class StreamingAPI_ReadBinary$Type extends MessageType /* int64 offset = 2; */ if (message.offset !== 0n) writer.tag(2, WireType.Varint).int64(message.offset); - /* bytes resource_signature = 3; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 3; */ + if (message.resourceSignature !== undefined) writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional uint32 chunk_size = 11; */ if (message.chunkSize !== undefined) @@ -296,7 +295,7 @@ class StreamingAPI_ReadText$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.ReadText", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 3, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 3, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 20, name: "read_limit", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 21, name: "search", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, @@ -306,7 +305,6 @@ class StreamingAPI_ReadText$Type extends MessageType { create(value?: PartialMessage): StreamingAPI_ReadText { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.offset = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -320,7 +318,7 @@ class StreamingAPI_ReadText$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 3: + case /* optional bytes resource_signature */ 3: message.resourceSignature = reader.bytes(); break; case /* int64 offset */ 2: @@ -353,8 +351,8 @@ class StreamingAPI_ReadText$Type extends MessageType { /* int64 offset = 2; */ if (message.offset !== 0n) writer.tag(2, WireType.Varint).int64(message.offset); - /* bytes resource_signature = 3; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 3; */ + if (message.resourceSignature !== undefined) writer.tag(3, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional int64 read_limit = 20; */ if (message.readLimit !== undefined) @@ -380,7 +378,7 @@ class StreamingAPI_LastLines$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.StreamingAPI.LastLines", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 4, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ }, + { no: 4, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ }, { no: 2, name: "offset", kind: "scalar", opt: true, T: 3 /*ScalarType.INT64*/, L: 0 /*LongType.BIGINT*/ }, { no: 3, name: "line_count", kind: "scalar", opt: true, T: 5 /*ScalarType.INT32*/ }, { no: 21, name: "search", kind: "scalar", opt: true, T: 9 /*ScalarType.STRING*/ }, @@ -390,7 +388,6 @@ class StreamingAPI_LastLines$Type extends MessageType { create(value?: PartialMessage): StreamingAPI_LastLines { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -403,7 +400,7 @@ class StreamingAPI_LastLines$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 4: + case /* optional bytes resource_signature */ 4: message.resourceSignature = reader.bytes(); break; case /* optional int64 offset */ 2: @@ -439,8 +436,8 @@ class StreamingAPI_LastLines$Type extends MessageType { /* optional int32 line_count = 3; */ if (message.lineCount !== undefined) writer.tag(3, WireType.Varint).int32(message.lineCount); - /* bytes resource_signature = 4; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 4; */ + if (message.resourceSignature !== undefined) writer.tag(4, WireType.LengthDelimited).bytes(message.resourceSignature); /* optional string search = 21; */ if (message.search !== undefined) diff --git a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts index d303cddd4f..76a1a783a1 100644 --- a/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts +++ b/lib/node/pl-drivers/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/uploadapi/protocol.ts @@ -34,9 +34,9 @@ export interface UploadAPI_Init_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Init.Response @@ -99,9 +99,9 @@ export interface UploadAPI_UpdateProgress_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 3 + * @generated from protobuf field: optional bytes resource_signature = 3 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Number of bytes uploaded since the earlier call to UpdateProgress. * This value is just blindly added to the 'bytes_processed' of the progress report, @@ -140,9 +140,9 @@ export interface UploadAPI_GetPartURL_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 6 + * @generated from protobuf field: optional bytes resource_signature = 6 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * Part to be uploaded. It is the responsibility of the client to keep track of already uploaded parts: * - client can request a URL for the same part twice (request -> request) without errors; @@ -247,9 +247,9 @@ export interface UploadAPI_Finalize_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 4 + * @generated from protobuf field: optional bytes resource_signature = 4 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; /** * The client can send the final checksum of the whole file to verify the upload. * The algorithm used to calculate this final checksum must match the algorithm from @@ -289,9 +289,9 @@ export interface UploadAPI_Reset_Request { /** * Signature proving the caller is authorized to access this resource. * - * @generated from protobuf field: bytes resource_signature = 2 + * @generated from protobuf field: optional bytes resource_signature = 2 */ - resourceSignature: Uint8Array; + resourceSignature?: Uint8Array; } /** * @generated from protobuf message MiLaboratories.Controller.Shared.UploadAPI.Reset.Response @@ -392,13 +392,12 @@ class UploadAPI_Init_Request$Type extends MessageType { constructor() { super("MiLaboratories.Controller.Shared.UploadAPI.Init.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 2, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): UploadAPI_Init_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -411,7 +410,7 @@ class UploadAPI_Init_Request$Type extends MessageType { case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 2: + case /* optional bytes resource_signature */ 2: message.resourceSignature = reader.bytes(); break; default: @@ -429,8 +428,8 @@ class UploadAPI_Init_Request$Type extends MessageType { /* uint64 resource_id = 1; */ if (message.resourceId !== 0n) writer.tag(1, WireType.Varint).uint64(message.resourceId); - /* bytes resource_signature = 2; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 2; */ + if (message.resourceSignature !== undefined) writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) @@ -572,14 +571,13 @@ class UploadAPI_UpdateProgress_Request$Type extends MessageType): UploadAPI_UpdateProgress_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.bytesProcessed = 0n; if (value !== undefined) reflectionMergePartial(this, message, value); @@ -593,7 +591,7 @@ class UploadAPI_UpdateProgress_Request$Type extends MessageType): UploadAPI_GetPartURL_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.partNumber = 0n; message.uploadedPartSize = 0n; message.isInternalUse = false; @@ -738,7 +735,7 @@ class UploadAPI_GetPartURL_Request$Type extends MessageType ["MiLaboratories.Controller.Shared.UploadAPI.ChecksumAlgorithm", UploadAPI_ChecksumAlgorithm, "CHECKSUM_ALGORITHM_"] }, { no: 3, name: "checksum", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } ]); @@ -978,7 +975,6 @@ class UploadAPI_Finalize_Request$Type extends MessageType): UploadAPI_Finalize_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); message.checksumAlgorithm = 0; message.checksum = new Uint8Array(0); if (value !== undefined) @@ -993,7 +989,7 @@ class UploadAPI_Finalize_Request$Type extends MessageType constructor() { super("MiLaboratories.Controller.Shared.UploadAPI.Reset.Request", [ { no: 1, name: "resource_id", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ }, - { no: 2, name: "resource_signature", kind: "scalar", T: 12 /*ScalarType.BYTES*/ } + { no: 2, name: "resource_signature", kind: "scalar", opt: true, T: 12 /*ScalarType.BYTES*/ } ]); } create(value?: PartialMessage): UploadAPI_Reset_Request { const message = globalThis.Object.create((this.messagePrototype!)); message.resourceId = 0n; - message.resourceSignature = new Uint8Array(0); if (value !== undefined) reflectionMergePartial(this, message, value); return message; @@ -1136,7 +1131,7 @@ class UploadAPI_Reset_Request$Type extends MessageType case /* uint64 resource_id */ 1: message.resourceId = reader.uint64().toBigInt(); break; - case /* bytes resource_signature */ 2: + case /* optional bytes resource_signature */ 2: message.resourceSignature = reader.bytes(); break; default: @@ -1154,8 +1149,8 @@ class UploadAPI_Reset_Request$Type extends MessageType /* uint64 resource_id = 1; */ if (message.resourceId !== 0n) writer.tag(1, WireType.Varint).uint64(message.resourceId); - /* bytes resource_signature = 2; */ - if (message.resourceSignature.length) + /* optional bytes resource_signature = 2; */ + if (message.resourceSignature !== undefined) writer.tag(2, WireType.LengthDelimited).bytes(message.resourceSignature); let u = options.writeUnknownFields; if (u !== false) From c01dc4bb702fd3fd152975c6d2c850605eaf5365 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Tue, 7 Apr 2026 00:09:32 +0200 Subject: [PATCH 11/27] MILAB-5815: ref: remove signature placeholders --- .../pl-client/src/core/ll_transaction.test.ts | 14 ++----------- lib/node/pl-client/src/core/transaction.ts | 21 ++++--------------- 2 files changed, 6 insertions(+), 29 deletions(-) diff --git a/lib/node/pl-client/src/core/ll_transaction.test.ts b/lib/node/pl-client/src/core/ll_transaction.test.ts index c2a3eca09b..b1e35e2a89 100644 --- a/lib/node/pl-client/src/core/ll_transaction.test.ts +++ b/lib/node/pl-client/src/core/ll_transaction.test.ts @@ -115,7 +115,6 @@ test("check timeout error type (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, - colorProof: new Uint8Array(0), }, }, false, @@ -126,11 +125,7 @@ test("check timeout error type (active)", async () => { const vr = await tx.send( { oneofKind: "resourceGet", - resourceGet: { - resourceId: id, - loadFields: false, - resourceSignature: new Uint8Array(0), - }, + resourceGet: { resourceId: id, loadFields: false }, }, false, ); @@ -179,7 +174,6 @@ test("check is abort error (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, - colorProof: new Uint8Array(0), }, }, false, @@ -190,11 +184,7 @@ test("check is abort error (active)", async () => { const vr = await tx.send( { oneofKind: "resourceGet", - resourceGet: { - resourceId: id, - loadFields: false, - resourceSignature: new Uint8Array(0), - }, + resourceGet: { resourceId: id, loadFields: false }, }, false, ); diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 83e111421d..3237102741 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -366,7 +366,6 @@ export class PlTransaction { id: localId, data: Buffer.from(name), errorIfExists, - colorProof: new Uint8Array(0), }, }, (r) => r.resourceCreateSingleton.resourceId as ResourceId, @@ -430,7 +429,6 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, - colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateStruct.resourceId, @@ -449,7 +447,6 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, - colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateEphemeral.resourceId, @@ -472,7 +469,6 @@ export class PlTransaction { id: localId, data: typeof data === "string" ? Buffer.from(data) : data, errorIfExists, - colorProof: new Uint8Array(0), }, }), (r) => r.resourceCreateValue.resourceId, @@ -524,7 +520,7 @@ export class PlTransaction { public removeResource(rId: ResourceId): void { this.sendVoidAsync({ oneofKind: "resourceRemove", - resourceRemove: { resourceId: rId, resourceSignature: new Uint8Array(0) }, + resourceRemove: { resourceId: rId }, }); } @@ -532,7 +528,7 @@ export class PlTransaction { return this.sendSingleAndParse( { oneofKind: "resourceExists", - resourceExists: { resourceId: rId, resourceSignature: new Uint8Array(0) }, + resourceExists: { resourceId: rId }, }, (r) => r.resourceExists.exists, ); @@ -594,7 +590,6 @@ export class PlTransaction { resourceGet: { resourceId: toResourceId(rId), loadFields: loadFields, - resourceSignature: new Uint8Array(0), }, }, (r) => protoToResource(notEmpty(r.resourceGet.resource)), @@ -679,7 +674,7 @@ export class PlTransaction { this._stat.inputsLocked++; this.sendVoidAsync({ oneofKind: "resourceLockInputs", - resourceLockInputs: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0) }, + resourceLockInputs: { resourceId: toResourceId(rId) }, }); } @@ -691,7 +686,7 @@ export class PlTransaction { this._stat.outputsLocked++; this.sendVoidAsync({ oneofKind: "resourceLockOutputs", - resourceLockOutputs: { resourceId: toResourceId(rId), resourceSignature: new Uint8Array(0) }, + resourceLockOutputs: { resourceId: toResourceId(rId) }, }); } @@ -705,9 +700,7 @@ export class PlTransaction { oneofKind: "resourceSetError", resourceSetError: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), errorResourceId: toResourceId(ref), - errorResourceSignature: new Uint8Array(0), }, }); } @@ -765,7 +758,6 @@ export class PlTransaction { fieldSetError: { field: toFieldId(fId), errorResourceId: toResourceId(ref), - errorResourceSignature: new Uint8Array(0), }, }); } @@ -801,7 +793,6 @@ export class PlTransaction { oneofKind: "resourceKeyValueList", resourceKeyValueList: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), startFrom: "", limit: 0, }, @@ -843,7 +834,6 @@ export class PlTransaction { oneofKind: "resourceKeyValueSet", resourceKeyValueSet: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), key, value: toBytes(value), }, @@ -855,7 +845,6 @@ export class PlTransaction { oneofKind: "resourceKeyValueDelete", resourceKeyValueDelete: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), key, }, }); @@ -868,7 +857,6 @@ export class PlTransaction { oneofKind: "resourceKeyValueGet", resourceKeyValueGet: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), key, }, }, @@ -900,7 +888,6 @@ export class PlTransaction { oneofKind: "resourceKeyValueGetIfExists", resourceKeyValueGetIfExists: { resourceId: toResourceId(rId), - resourceSignature: new Uint8Array(0), key, }, }, From 4e6f65f5e75e771d8c6bc9907483e231d4c5d582 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Tue, 7 Apr 2026 02:22:41 +0200 Subject: [PATCH 12/27] MILAB-5825: feat: signature cache --- lib/node/pl-client/src/core/client.ts | 11 + lib/node/pl-client/src/core/errors.ts | 23 +++ .../pl-client/src/core/signature_cache.ts | 29 +++ lib/node/pl-client/src/core/transaction.ts | 190 ++++++++++++++---- .../pl-client/src/core/type_conversion.ts | 3 + lib/node/pl-client/src/core/types.ts | 10 + lib/node/pl-client/src/index.ts | 1 + 7 files changed, 233 insertions(+), 34 deletions(-) create mode 100644 lib/node/pl-client/src/core/signature_cache.ts diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index 2df5a89a29..44a76db25b 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -26,6 +26,7 @@ import { addStat, initialTxStat } from "./stat"; import type { WireConnection } from "./wire"; import { advisoryLock } from "./advisory_locks"; import { plAddressToConfig } from "./config"; +import { SignatureCache } from "./signature_cache"; export type TxOps = PlCallOps & { sync?: boolean; @@ -90,6 +91,9 @@ export class PlClient { /** Resource data cache, to minimize redundant data rereading from remote db */ private readonly resourceDataCache: LRUCache; + /** Cross-transaction signature cache */ + private readonly _signatureCache = new SignatureCache(); + private constructor( configOrAddress: PlClientConfig | string, auth: AuthOps, @@ -203,6 +207,12 @@ export class PlClient { return this._serverInfo!; } + /** Shared signature cache, persists across transactions. + * Call clear() on auth errors to invalidate stale signatures. */ + public get signatureCache(): SignatureCache { + return this._signatureCache; + } + /** Currently implements custom logic to emulate future behaviour with single root. */ private async init() { if (this.initialized) throw new Error("Already initialized"); @@ -298,6 +308,7 @@ export class PlClient { clientRoot, this.finalPredicate, this.resourceDataCache, + this._signatureCache, ); let ok = false; diff --git a/lib/node/pl-client/src/core/errors.ts b/lib/node/pl-client/src/core/errors.ts index 9ed4e310bc..fc18c2ce2e 100644 --- a/lib/node/pl-client/src/core/errors.ts +++ b/lib/node/pl-client/src/core/errors.ts @@ -25,6 +25,18 @@ export function isUnauthenticated(err: unknown, nested: boolean = false): boolea return false; } +export function isPermissionDenied(err: unknown, nested: boolean = false): boolean { + if (err === undefined || err === null) return false; + + if (err instanceof PermissionDeniedError) return true; + if ((err as any).name == "RpcError" && (err as any).code == "PERMISSION_DENIED") return true; + if ((err as any).name == "RESTError" && (err as any).status.code == Code.PERMISSION_DENIED) + return true; + if ((err as any).cause !== undefined && !nested) + return isPermissionDenied((err as any).cause, true); + return false; +} + export function isTimeoutError(err: unknown, nested: boolean = false): boolean { if (err === undefined || err === null) return false; @@ -114,6 +126,13 @@ export class UnauthenticatedError extends Error { } } +export class PermissionDeniedError extends Error { + name = "PermissionDeniedError"; + constructor(message: string) { + super("PermissionDenied: " + message); + } +} + export class DisconnectedError extends Error { name = "DisconnectedError"; constructor(message: string) { @@ -133,6 +152,10 @@ export function rethrowMeaningfulError(error: any, wrapIfUnknown: boolean = fals if (error instanceof UnauthenticatedError) throw error; throw new UnauthenticatedError(error.message); } + if (isPermissionDenied(error)) { + if (error instanceof PermissionDeniedError) throw error; + throw new PermissionDeniedError(error.message); + } if (isConnectionProblem(error)) { if (error instanceof DisconnectedError) throw error; throw new DisconnectedError(error.message); diff --git a/lib/node/pl-client/src/core/signature_cache.ts b/lib/node/pl-client/src/core/signature_cache.ts new file mode 100644 index 0000000000..cea37b9520 --- /dev/null +++ b/lib/node/pl-client/src/core/signature_cache.ts @@ -0,0 +1,29 @@ +/** Cross-transaction cache for resource signatures. + * Keyed by resource ID (bigint), stores opaque signature bytes (Uint8Array). */ +export class SignatureCache { + private readonly store = new Map(); + + set(id: bigint, sig: Uint8Array): void { + this.store.set(id, sig); + } + + get(id: bigint): Uint8Array | undefined { + return this.store.get(id); + } + + delete(id: bigint): boolean { + return this.store.delete(id); + } + + clear(): void { + this.store.clear(); + } + + has(id: bigint): boolean { + return this.store.has(id); + } + + get size(): number { + return this.store.size; + } +} diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 3237102741..e526749445 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -40,6 +40,7 @@ import { isNotFoundError } from "./errors"; import type { FinalResourceDataPredicate } from "./final"; import type { LRUCache } from "lru-cache"; import type { ResourceDataCacheRecord } from "./cache"; +import type { SignatureCache } from "./signature_cache"; import type { TxStat } from "./stat"; import { initialTxStatWithoutTime } from "./stat"; import type { ErrorResourceData } from "./error_resource"; @@ -164,6 +165,12 @@ export class PlTransaction { private localResourceIdCounter = 0; + /** Maps resource ids (both local and global) to their signatures received from the server */ + private readonly signatureStore = new Map(); + + /** Default color proof to include in resource creation requests */ + private defaultColorProof?: Uint8Array; + /** Store logical tx open / closed state to prevent invalid sequence of requests. * True means output stream was completed. * Contract: there must be no async operations between setting this field to true and sending complete signal to stream. */ @@ -189,6 +196,7 @@ export class PlTransaction { private readonly _clientRoot: OptionalResourceId, private readonly finalPredicate: FinalResourceDataPredicate, private readonly sharedResourceDataCache: LRUCache, + private readonly sharedSignatureCache?: SignatureCache, private readonly enableFormattedErrors: boolean = false, ) { // initiating transaction @@ -276,6 +284,44 @@ export class PlTransaction { void this.track(this.sendVoidSync(r)); } + private storeSignature(id: bigint, sig?: Uint8Array): void { + if (sig && sig.length > 0) { + this.signatureStore.set(id, sig); + this.sharedSignatureCache?.set(id, sig); + } + } + + private getSignature(rId: bigint): Uint8Array | undefined { + return this.signatureStore.get(rId) ?? this.sharedSignatureCache?.get(rId); + } + + private toSignedFieldId( + fId: AnyFieldRef, + ): { resourceId: bigint; fieldName: string; resourceSignature?: Uint8Array } { + const base = toFieldId(fId); + return { ...base, resourceSignature: this.getSignature(base.resourceId) }; + } + + private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { + this.storeSignature(result.id, result.resourceSignature); + const rd = result as ResourceData; + if (rd.fields) { + for (const f of rd.fields) { + if (!isNullResourceId(f.value)) this.storeSignature(f.value, f.valueSignature); + if (!isNullResourceId(f.error)) this.storeSignature(f.error, f.errorSignature); + } + } + } + + /** Set default color proof for subsequent resource creation requests */ + public setDefaultColor(colorProof: Uint8Array): void { + this.defaultColorProof = colorProof; + this.sendVoidAsync({ + oneofKind: "setDefaultColor", + setDefaultColor: { colorProof }, + }); + } + private checkTxOpen() { if (this._completed) throw new Error("Transaction already closed"); } @@ -355,6 +401,7 @@ export class PlTransaction { name: string, type: ResourceType, errorIfExists: boolean = false, + colorProof?: Uint8Array, ): ResourceRef { const localId = this.nextLocalResourceId(false); @@ -366,9 +413,16 @@ export class PlTransaction { id: localId, data: Buffer.from(name), errorIfExists, + colorProof: colorProof ?? this.defaultColorProof, }, }, - (r) => r.resourceCreateSingleton.resourceId as ResourceId, + (r) => { + const sig = r.resourceCreateSingleton.resourceSignature; + const id = r.resourceCreateSingleton.resourceId; + this.storeSignature(id, sig); + this.storeSignature(localId, sig); + return id as ResourceId; + }, ); void this.track(globalId); @@ -390,7 +444,11 @@ export class PlTransaction { loadFields, }, }, - (r) => protoToResource(notEmpty(r.resourceGetSingleton.resource)), + (r) => { + const result = protoToResource(notEmpty(r.resourceGetSingleton.resource)); + this.storeSignaturesFromResourceData(result); + return result; + }, ); } @@ -398,10 +456,19 @@ export class PlTransaction { root: boolean, req: (localId: LocalResourceId) => OneOfKind, parser: (resp: OneOfKind) => bigint, + sigExtractor?: (resp: OneOfKind) => Uint8Array | undefined, ): ResourceRef { const localId = this.nextLocalResourceId(root); - const globalId = this.sendSingleAndParse(req(localId), (r) => parser(r) as ResourceId); + const globalId = this.sendSingleAndParse(req(localId), (r) => { + const id = parser(r) as ResourceId; + if (sigExtractor) { + const sig = sigExtractor(r); + this.storeSignature(id, sig); + this.storeSignature(localId, sig); + } + return id; + }); void this.track(globalId); @@ -414,10 +481,15 @@ export class PlTransaction { true, (localId) => ({ oneofKind: "resourceCreateRoot", resourceCreateRoot: { type, id: localId } }), (r) => r.resourceCreateRoot.resourceId, + (r) => r.resourceCreateRoot.resourceSignature, ); } - public createStruct(type: ResourceType, data?: Uint8Array | string): ResourceRef { + public createStruct( + type: ResourceType, + data?: Uint8Array | string, + colorProof?: Uint8Array, + ): ResourceRef { this._stat.structsCreated++; this._stat.structsCreatedDataBytes += data?.length ?? 0; return this.createResource( @@ -429,13 +501,19 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, + colorProof: colorProof ?? this.defaultColorProof, }, }), (r) => r.resourceCreateStruct.resourceId, + (r) => r.resourceCreateStruct.resourceSignature, ); } - public createEphemeral(type: ResourceType, data?: Uint8Array | string): ResourceRef { + public createEphemeral( + type: ResourceType, + data?: Uint8Array | string, + colorProof?: Uint8Array, + ): ResourceRef { this._stat.ephemeralsCreated++; this._stat.ephemeralsCreatedDataBytes += data?.length ?? 0; return this.createResource( @@ -447,9 +525,11 @@ export class PlTransaction { id: localId, data: data === undefined ? undefined : typeof data === "string" ? Buffer.from(data) : data, + colorProof: colorProof ?? this.defaultColorProof, }, }), (r) => r.resourceCreateEphemeral.resourceId, + (r) => r.resourceCreateEphemeral.resourceSignature, ); } @@ -457,6 +537,7 @@ export class PlTransaction { type: ResourceType, data: Uint8Array | string, errorIfExists: boolean = false, + colorProof?: Uint8Array, ): ResourceRef { this._stat.valuesCreated++; this._stat.valuesCreatedDataBytes += data?.length ?? 0; @@ -469,9 +550,11 @@ export class PlTransaction { id: localId, data: typeof data === "string" ? Buffer.from(data) : data, errorIfExists, + colorProof: colorProof ?? this.defaultColorProof, }, }), (r) => r.resourceCreateValue.resourceId, + (r) => r.resourceCreateValue.resourceSignature, ); } @@ -493,9 +576,10 @@ export class PlTransaction { } public setResourceName(name: string, rId: AnyResourceRef): void { + const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceNameSet", - resourceNameSet: { resourceId: toResourceId(rId), name }, + resourceNameSet: { resourceId: id, resourceSignature: this.getSignature(id), name }, }); } @@ -506,7 +590,11 @@ export class PlTransaction { public getResourceByName(name: string): Promise { return this.sendSingleAndParse( { oneofKind: "resourceNameGet", resourceNameGet: { name } }, - (r) => ensureResourceIdNotNull(r.resourceNameGet.resourceId as OptionalResourceId), + (r) => { + const id = ensureResourceIdNotNull(r.resourceNameGet.resourceId as OptionalResourceId); + this.storeSignature(id, r.resourceNameGet.resourceSignature); + return id; + }, ); } @@ -520,7 +608,7 @@ export class PlTransaction { public removeResource(rId: ResourceId): void { this.sendVoidAsync({ oneofKind: "resourceRemove", - resourceRemove: { resourceId: rId }, + resourceRemove: { resourceId: rId, resourceSignature: this.getSignature(rId) }, }); } @@ -528,7 +616,7 @@ export class PlTransaction { return this.sendSingleAndParse( { oneofKind: "resourceExists", - resourceExists: { resourceId: rId }, + resourceExists: { resourceId: rId, resourceSignature: this.getSignature(rId) }, }, (r) => r.resourceExists.exists, ); @@ -574,25 +662,33 @@ export class PlTransaction { if (!loadFields) { this._stat.rGetDataCacheHits++; this._stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0; + this.storeSignaturesFromResourceData(fromCache.basicData); return fromCache.basicData; } else if (fromCache.data) { this._stat.rGetDataCacheHits++; this._stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0; this._stat.rGetDataCacheFields += fromCache.data.fields.length; + this.storeSignaturesFromResourceData(fromCache.data); return fromCache.data; } } } + const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceGet", resourceGet: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), loadFields: loadFields, }, }, - (r) => protoToResource(notEmpty(r.resourceGet.resource)), + (r) => { + const rd = protoToResource(notEmpty(r.resourceGet.resource)); + this.storeSignaturesFromResourceData(rd); + return rd; + }, ); this._stat.rGetDataNetRequests++; @@ -672,9 +768,10 @@ export class PlTransaction { */ public lockInputs(rId: AnyResourceRef): void { this._stat.inputsLocked++; + const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceLockInputs", - resourceLockInputs: { resourceId: toResourceId(rId) }, + resourceLockInputs: { resourceId: id, resourceSignature: this.getSignature(id) }, }); } @@ -684,9 +781,10 @@ export class PlTransaction { */ public lockOutputs(rId: AnyResourceRef): void { this._stat.outputsLocked++; + const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceLockOutputs", - resourceLockOutputs: { resourceId: toResourceId(rId) }, + resourceLockOutputs: { resourceId: id, resourceSignature: this.getSignature(id) }, }); } @@ -696,11 +794,15 @@ export class PlTransaction { } public setResourceError(rId: AnyResourceRef, ref: AnyResourceRef): void { + const id = toResourceId(rId); + const errorId = toResourceId(ref); this.sendVoidAsync({ oneofKind: "resourceSetError", resourceSetError: { - resourceId: toResourceId(rId), - errorResourceId: toResourceId(ref), + resourceId: id, + resourceSignature: this.getSignature(id), + errorResourceId: errorId, + errorResourceSignature: this.getSignature(errorId), }, }); } @@ -713,7 +815,7 @@ export class PlTransaction { this._stat.fieldsCreated++; this.sendVoidAsync({ oneofKind: "fieldCreate", - fieldCreate: { type: fieldTypeToProto(fieldType), id: toFieldId(fId) }, + fieldCreate: { type: fieldTypeToProto(fieldType), id: this.toSignedFieldId(fId) }, }); if (value !== undefined) this.setField(fId, value); } @@ -722,7 +824,7 @@ export class PlTransaction { return this.sendSingleAndParse( { oneofKind: "fieldExists", - fieldExists: { field: toFieldId(fId) }, + fieldExists: { field: this.toSignedFieldId(fId) }, }, (r) => r.fieldExists.exists, ); @@ -730,34 +832,39 @@ export class PlTransaction { public setField(fId: AnyFieldRef, ref: AnyRef): void { this._stat.fieldsSet++; - if (isResource(ref)) + if (isResource(ref)) { + const refId = toResourceId(ref); this.sendVoidAsync({ oneofKind: "fieldSet", fieldSet: { - field: toFieldId(fId), + field: this.toSignedFieldId(fId), value: { - resourceId: toResourceId(ref), + resourceId: refId, + resourceSignature: this.getSignature(refId), fieldName: "", // default value, read as undefined }, }, }); - else + } else { this.sendVoidAsync({ oneofKind: "fieldSet", fieldSet: { - field: toFieldId(fId), - value: toFieldId(ref), + field: this.toSignedFieldId(fId), + value: this.toSignedFieldId(ref), }, }); + } } public setFieldError(fId: AnyFieldRef, ref: AnyResourceRef): void { this._stat.fieldsSet++; + const errorId = toResourceId(ref); this.sendVoidAsync({ oneofKind: "fieldSetError", fieldSetError: { - field: toFieldId(fId), - errorResourceId: toResourceId(ref), + field: this.toSignedFieldId(fId), + errorResourceId: errorId, + errorResourceSignature: this.getSignature(errorId), }, }); } @@ -765,8 +872,13 @@ export class PlTransaction { public getField(fId: AnyFieldRef): Promise { this._stat.fieldsGet++; return this.sendSingleAndParse( - { oneofKind: "fieldGet", fieldGet: { field: toFieldId(fId) } }, - (r) => protoToField(notEmpty(r.fieldGet.field)), + { oneofKind: "fieldGet", fieldGet: { field: this.toSignedFieldId(fId) } }, + (r) => { + const fd = protoToField(notEmpty(r.fieldGet.field)); + if (!isNullResourceId(fd.value)) this.storeSignature(fd.value, fd.valueSignature); + if (!isNullResourceId(fd.error)) this.storeSignature(fd.error, fd.errorSignature); + return fd; + }, ); } @@ -775,11 +887,11 @@ export class PlTransaction { } public resetField(fId: AnyFieldRef): void { - this.sendVoidAsync({ oneofKind: "fieldReset", fieldReset: { field: toFieldId(fId) } }); + this.sendVoidAsync({ oneofKind: "fieldReset", fieldReset: { field: this.toSignedFieldId(fId) } }); } public removeField(fId: AnyFieldRef): void { - this.sendVoidAsync({ oneofKind: "fieldRemove", fieldRemove: { field: toFieldId(fId) } }); + this.sendVoidAsync({ oneofKind: "fieldRemove", fieldRemove: { field: this.toSignedFieldId(fId) } }); } // @@ -788,11 +900,13 @@ export class PlTransaction { public async listKeyValues(rId: AnyResourceRef): Promise { return this.track(async () => { + const id = toResourceId(rId); const result = await this.sendMultiAndParse( { oneofKind: "resourceKeyValueList", resourceKeyValueList: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), startFrom: "", limit: 0, }, @@ -830,10 +944,12 @@ export class PlTransaction { public setKValue(rId: AnyResourceRef, key: string, value: Uint8Array | string): void { this._stat.kvSetRequests++; this._stat.kvSetBytes++; + const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceKeyValueSet", resourceKeyValueSet: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), key, value: toBytes(value), }, @@ -841,10 +957,12 @@ export class PlTransaction { } public deleteKValue(rId: AnyResourceRef, key: string): void { + const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceKeyValueDelete", resourceKeyValueDelete: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), key, }, }); @@ -852,11 +970,13 @@ export class PlTransaction { public async getKValue(rId: AnyResourceRef, key: string): Promise { return this.track(async () => { + const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGet", resourceKeyValueGet: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), key, }, }, @@ -883,11 +1003,13 @@ export class PlTransaction { key: string, ): Promise { return this.track(async () => { + const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGetIfExists", resourceKeyValueGetIfExists: { - resourceId: toResourceId(rId), + resourceId: id, + resourceSignature: this.getSignature(id), key, }, }, diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index f08b62eb19..cce4cfeb41 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -40,6 +40,7 @@ export function protoToResource(proto: Resource): ResourceData { kind: protoToResourceKind(proto.kind), error: protoToError(proto), final: proto.isFinal, + resourceSignature: proto.resourceSignature, fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField), }; } @@ -68,6 +69,8 @@ export function protoToField(proto: Field): FieldData { value: proto.value as OptionalResourceId, error: proto.error as OptionalResourceId, valueIsFinal: proto.valueIsFinal, + valueSignature: proto.valueSignature, + errorSignature: proto.errorSignature, }; } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index ceb30c49c2..ba7c2b8a6c 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -87,6 +87,9 @@ export type BasicResourceData = { /** This value is derived from resource state by the server and can be used as * a robust criteria to determine resource is in final state. */ readonly final: boolean; + + /** Signature for this resource, used for authorization in subsequent requests. */ + readonly resourceSignature?: Uint8Array; }; export function extractBasicResourceData(rd: ResourceData): BasicResourceData { @@ -101,6 +104,7 @@ export function extractBasicResourceData(rd: ResourceData): BasicResourceData { outputsLocked, resourceReady, final, + resourceSignature, } = rd; return { id, @@ -113,6 +117,7 @@ export function extractBasicResourceData(rd: ResourceData): BasicResourceData { outputsLocked, resourceReady, final, + resourceSignature, }; } @@ -137,6 +142,11 @@ export type FieldData = { /** True if value the fields points to is in final state. */ readonly valueIsFinal: boolean; + + /** Signature for the value resource, inheriting parent resource's color. */ + readonly valueSignature?: Uint8Array; + /** Signature for the error resource, inheriting parent resource's color. */ + readonly errorSignature?: Uint8Array; }; // diff --git a/lib/node/pl-client/src/index.ts b/lib/node/pl-client/src/index.ts index bfd16cc64a..a0aa1470d8 100644 --- a/lib/node/pl-client/src/index.ts +++ b/lib/node/pl-client/src/index.ts @@ -5,6 +5,7 @@ export * from "./core/client"; export * from "./core/driver"; export * from "./core/transaction"; export * from "./core/errors"; +export * from "./core/signature_cache"; export * from "./core/default_client"; export * from "./core/unauth_client"; export * from "./core/auth"; From c05891e68c9b7abb2cf422867b13695834bdf94c Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 15:05:06 +0200 Subject: [PATCH 13/27] MILAB-5815: fix: test sign propogation --- lib/node/pl-client/src/core/client.ts | 17 ++++++++ .../pl-client/src/core/ll_transaction.test.ts | 40 ++++++++++++++++--- lib/node/pl-client/src/test/test_config.ts | 7 +++- 3 files changed, 56 insertions(+), 8 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index 3337ede8c0..b25991152b 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -242,11 +242,13 @@ export class PlClient { // Try ListUserResources first (new backend, gRPC only) let rootFromServer: ResourceId | undefined; + let rootSignature: Uint8Array | undefined; try { const responses = await this._ll.listUserResources({ limit: 1 }); for (const msg of responses) { if (msg.entry.oneofKind === "userRoot") { rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId); + rootSignature = msg.entry.userRoot.resourceSignature; break; } } @@ -256,6 +258,12 @@ export class PlClient { } if (rootFromServer !== undefined) { + // Store root signature in cross-transaction cache so subsequent + // transactions can attach it to requests and use it as color proof. + if (rootSignature && rootSignature.length > 0) { + this._signatureCache.set(rootFromServer, rootSignature); + } + // New path: server created/returned the root if (this.conf.alternativeRoot === undefined) { this._clientRoot = rootFromServer; @@ -361,6 +369,15 @@ export class PlClient { this._signatureCache, ); + // Auto-set default color proof for write transactions so that + // resource creation carries the correct access color. + if (writable && !isNullResourceId(clientRoot)) { + const rootSig = this._signatureCache.get(clientRoot); + if (rootSig) { + tx.setDefaultColor(rootSig); + } + } + let ok = false; let result: T | undefined = undefined; let txId; diff --git a/lib/node/pl-client/src/core/ll_transaction.test.ts b/lib/node/pl-client/src/core/ll_transaction.test.ts index c2a3eca09b..e37b92cf25 100644 --- a/lib/node/pl-client/src/core/ll_transaction.test.ts +++ b/lib/node/pl-client/src/core/ll_transaction.test.ts @@ -5,6 +5,18 @@ import { test, expect } from "vitest"; import { isTimeoutOrCancelError } from "./errors"; import { Aborted } from "@milaboratories/ts-helpers"; +import type { LLPlClient } from "./ll_client"; + +/** Get root resource signature from ListUserResources for use as color proof in tests. */ +async function getRootSignature(client: LLPlClient): Promise { + const responses = await client.listUserResources({ limit: 1 }); + for (const msg of responses) { + if (msg.entry.oneofKind === "userRoot" && msg.entry.userRoot.resourceSignature) { + return msg.entry.userRoot.resourceSignature; + } + } + return new Uint8Array(0); +} test("check successful transaction", async () => { const client = await getTestLLClient(); @@ -80,6 +92,7 @@ test("check timeout error type (passive)", async () => { test("check timeout error type (active)", async () => { const client = await getTestLLClient(); + const rootSig = await getRootSignature(client); const tx = client.createTx(true, { timeout: 500 }); try { @@ -96,6 +109,12 @@ test("check timeout error type (active)", async () => { ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); + // Set default color so resource creation succeeds in strict mode + await tx.send( + { oneofKind: "setDefaultColor", setDefaultColor: { colorProof: rootSig } }, + false, + ); + const rData = Uint8Array.from([ (Math.random() * 256) & 0xff, (Math.random() * 256) & 0xff, @@ -115,12 +134,13 @@ test("check timeout error type (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, - colorProof: new Uint8Array(0), }, }, false, ); - const id = (await createResponse).resourceCreateValue.resourceId; + const createResp = (await createResponse).resourceCreateValue; + const id = createResp.resourceId; + const resourceSignature = createResp.resourceSignature ?? new Uint8Array(0); while (true) { const vr = await tx.send( @@ -129,7 +149,7 @@ test("check timeout error type (active)", async () => { resourceGet: { resourceId: id, loadFields: false, - resourceSignature: new Uint8Array(0), + resourceSignature, }, }, false, @@ -144,6 +164,7 @@ test("check timeout error type (active)", async () => { test("check is abort error (active)", async () => { const client = await getTestLLClient(); + const rootSig = await getRootSignature(client); const tx = client.createTx(true, { abortSignal: AbortSignal.timeout(100) }); try { @@ -160,6 +181,12 @@ test("check is abort error (active)", async () => { ); expect(openResponse.txOpen.tx?.isValid).toBeTruthy(); + // Set default color so resource creation succeeds in strict mode + await tx.send( + { oneofKind: "setDefaultColor", setDefaultColor: { colorProof: rootSig } }, + false, + ); + const rData = Uint8Array.from([ Math.random() & 0xff, Math.random() & 0xff, @@ -179,12 +206,13 @@ test("check is abort error (active)", async () => { type: { name: "TestValue", version: "1" }, data: rData, errorIfExists: false, - colorProof: new Uint8Array(0), }, }, false, ); - const id = (await createResponse).resourceCreateValue.resourceId; + const createResp = (await createResponse).resourceCreateValue; + const id = createResp.resourceId; + const resourceSignature = createResp.resourceSignature ?? new Uint8Array(0); while (true) { const vr = await tx.send( @@ -193,7 +221,7 @@ test("check is abort error (active)", async () => { resourceGet: { resourceId: id, loadFields: false, - resourceSignature: new Uint8Array(0), + resourceSignature, }, }, false, diff --git a/lib/node/pl-client/src/test/test_config.ts b/lib/node/pl-client/src/test/test_config.ts index 0546f56ea9..3ce37f0ffd 100644 --- a/lib/node/pl-client/src/test/test_config.ts +++ b/lib/node/pl-client/src/test/test_config.ts @@ -89,8 +89,11 @@ function saveAuthInfoCallback(tConf: TestConfig): (authInformation: AuthInformat } const cleanAuthInfoCallback = () => { - console.warn(`Removing: ${getFullAuthDataFilePath()}`); - fs.rmSync(getFullAuthDataFilePath()); + const p = getFullAuthDataFilePath(); + if (fs.existsSync(p)) { + console.warn(`Removing: ${p}`); + fs.rmSync(p); + } }; export async function getTestClientConf(): Promise<{ conf: PlClientConfig; auth: AuthOps }> { From 9913614c4d201f6e681c91a5ec2ebe6e6123ed9d Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 17:35:00 +0200 Subject: [PATCH 14/27] MILAB-5815: feat: sigature for proxied api --- lib/node/pl-client/src/core/client.ts | 6 +-- lib/node/pl-drivers/src/clients/download.ts | 8 ++-- lib/node/pl-drivers/src/clients/logs.ts | 17 +++++--- lib/node/pl-drivers/src/clients/ls_api.ts | 5 ++- lib/node/pl-drivers/src/clients/progress.ts | 20 ++++++++-- lib/node/pl-drivers/src/clients/upload.ts | 39 ++++++++++++++----- .../download_blob/download_blob.test.ts | 1 + .../drivers/helpers/download_remote_handle.ts | 8 +++- .../src/drivers/helpers/logs_handle.ts | 14 ++++--- .../src/drivers/helpers/ls_storage_entry.ts | 1 + lib/node/pl-drivers/src/drivers/ls.ts | 21 ++++++++-- lib/node/pl-tree/src/accessors.ts | 3 +- lib/node/pl-tree/src/snapshot.ts | 1 + lib/node/pl-tree/src/state.ts | 6 +++ 14 files changed, 113 insertions(+), 37 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index b25991152b..e88d81153c 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -369,9 +369,9 @@ export class PlClient { this._signatureCache, ); - // Auto-set default color proof for write transactions so that - // resource creation carries the correct access color. - if (writable && !isNullResourceId(clientRoot)) { + // Auto-set default color proof so that resource creation (write TXs) + // and name lookups (read TXs) carry the correct access color. + if (!isNullResourceId(clientRoot)) { const rootSig = this._signatureCache.get(clientRoot); if (rootSig) { tx.setDefaultColor(rootSig); diff --git a/lib/node/pl-drivers/src/clients/download.ts b/lib/node/pl-drivers/src/clients/download.ts index 611c48c149..732916763e 100644 --- a/lib/node/pl-drivers/src/clients/download.ts +++ b/lib/node/pl-drivers/src/clients/download.ts @@ -129,7 +129,7 @@ export class ClientDownload { } private async grpcGetDownloadUrl( - { id, type }: ResourceInfo, + { id, type, resourceSignature }: ResourceInfo, options?: RpcOptions, signal?: AbortSignal, ): Promise { @@ -139,7 +139,7 @@ export class ClientDownload { const client = this.wire.get(); if (client instanceof DownloadClient) { return await client.getDownloadURL( - { resourceId: id, isInternalUse: false }, + { resourceId: id, resourceSignature, isInternalUse: false }, addRTypeToMetadata(type, withAbort), ).response; } else { @@ -147,7 +147,9 @@ export class ClientDownload { await client.POST("/v1/get-download-url", { body: { resourceId: id.toString(), - resourceSignature: "", + resourceSignature: resourceSignature + ? Buffer.from(resourceSignature).toString("base64") + : "", isInternalUse: false, }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/clients/logs.ts b/lib/node/pl-drivers/src/clients/logs.ts index 5683bd4292..34c7326c1e 100644 --- a/lib/node/pl-drivers/src/clients/logs.ts +++ b/lib/node/pl-drivers/src/clients/logs.ts @@ -37,7 +37,7 @@ export class ClientLogs { * the new offset * and the total size of the (currently existing) file. */ public async lastLines( - { id: rId, type: rType }: ResourceInfo, + { id: rId, type: rType, resourceSignature: rSig }: ResourceInfo, lineCount: number, offsetBytes: bigint = 0n, // if 0n, then start from the end. searchStr?: string, @@ -47,7 +47,13 @@ export class ClientLogs { if (client instanceof StreamingClient) { return ( await client.lastLines( - { resourceId: rId, lineCount: lineCount, offset: offsetBytes, search: searchStr }, + { + resourceId: rId, + resourceSignature: rSig, + lineCount: lineCount, + offset: offsetBytes, + search: searchStr, + }, addRTypeToMetadata(rType, options), ) ).response; @@ -57,7 +63,7 @@ export class ClientLogs { await client.POST("/v1/last-lines", { body: { resourceId: rId.toString(), - resourceSignature: "", + resourceSignature: rSig ? Buffer.from(rSig).toString("base64") : "", lineCount: lineCount, offset: offsetBytes.toString(), search: searchStr ?? "", @@ -78,7 +84,7 @@ export class ClientLogs { * the new offset * and the total size of the (currently existing) file. */ public async readText( - { id: rId, type: rType }: ResourceInfo, + { id: rId, type: rType, resourceSignature: rSig }: ResourceInfo, lineCount: number, offsetBytes: bigint = 0n, // if 0n, then start from the beginning. searchStr?: string, @@ -91,6 +97,7 @@ export class ClientLogs { await client.readText( { resourceId: notEmpty(rId), + resourceSignature: rSig, readLimit: BigInt(lineCount), offset: offsetBytes, search: searchStr, @@ -104,7 +111,7 @@ export class ClientLogs { await client.POST("/v1/read/text", { body: { resourceId: rId.toString(), - resourceSignature: "", + resourceSignature: rSig ? Buffer.from(rSig).toString("base64") : "", readLimit: lineCount.toString(), offset: offsetBytes.toString(), search: searchStr ?? "", diff --git a/lib/node/pl-drivers/src/clients/ls_api.ts b/lib/node/pl-drivers/src/clients/ls_api.ts index 4375033f57..23a6ae6659 100644 --- a/lib/node/pl-drivers/src/clients/ls_api.ts +++ b/lib/node/pl-drivers/src/clients/ls_api.ts @@ -46,6 +46,7 @@ export class ClientLs { return await client.list( { resourceId: rInfo.id, + resourceSignature: rInfo.resourceSignature, location: path, }, addRTypeToMetadata(rInfo.type, options), @@ -55,7 +56,9 @@ export class ClientLs { await client.POST("/v1/list", { body: { resourceId: rInfo.id.toString(), - resourceSignature: "", + resourceSignature: rInfo.resourceSignature + ? Buffer.from(rInfo.resourceSignature).toString("base64") + : "", location: path, }, headers: { ...createRTypeRoutingHeader(rInfo.type) }, diff --git a/lib/node/pl-drivers/src/clients/progress.ts b/lib/node/pl-drivers/src/clients/progress.ts index ce594ccb6c..478d463063 100644 --- a/lib/node/pl-drivers/src/clients/progress.ts +++ b/lib/node/pl-drivers/src/clients/progress.ts @@ -50,19 +50,31 @@ export class ClientProgress { close() {} /** getStatus gets a progress status by given rId and rType. */ - async getStatus({ id, type }: ResourceInfo, options?: RpcOptions): Promise { + async getStatus( + { id, type, resourceSignature }: ResourceInfo, + options?: RpcOptions, + ): Promise { const client = this.wire.get(); let report: ProgressAPI_Report; if (client instanceof ProgressClient) { report = notEmpty( - (await client.getStatus({ resourceId: id }, addRTypeToMetadata(type, options)).response) - .report, + ( + await client.getStatus( + { resourceId: id, resourceSignature }, + addRTypeToMetadata(type, options), + ).response + ).report, ); } else { const resp = ( await client.POST("/v1/get-progress", { - body: { resourceId: id.toString(), resourceSignature: "" }, + body: { + resourceId: id.toString(), + resourceSignature: resourceSignature + ? Buffer.from(resourceSignature).toString("base64") + : "", + }, headers: { ...createRTypeRoutingHeader(type) }, }) ).data!.report; diff --git a/lib/node/pl-drivers/src/clients/upload.ts b/lib/node/pl-drivers/src/clients/upload.ts index fcc0687dc5..b58ecdd56f 100644 --- a/lib/node/pl-drivers/src/clients/upload.ts +++ b/lib/node/pl-drivers/src/clients/upload.ts @@ -70,7 +70,7 @@ export class ClientUpload { close() {} public async initUpload( - { id, type }: ResourceInfo, + { id, type, resourceSignature }: ResourceInfo, options?: RpcOptions, ): Promise<{ overall: bigint; @@ -81,8 +81,12 @@ export class ClientUpload { const client = this.wire.get(); if (client instanceof UploadClient) { - const init = (await client.init({ resourceId: id }, addRTypeToMetadata(type, options))) - .response; + const init = ( + await client.init( + { resourceId: id, resourceSignature }, + addRTypeToMetadata(type, options), + ) + ).response; return { overall: init.partsCount, @@ -96,7 +100,9 @@ export class ClientUpload { await client.POST("/v1/upload/init", { body: { resourceId: id.toString(), - resourceSignature: "", + resourceSignature: resourceSignature + ? Buffer.from(resourceSignature).toString("base64") + : "", }, headers: { ...createRTypeRoutingHeader(type) }, }) @@ -111,7 +117,7 @@ export class ClientUpload { } public async partUpload( - { id, type }: ResourceInfo, + { id, type, resourceSignature }: ResourceInfo, path: string, expectedMTimeUnix: bigint, partNumber: bigint, @@ -128,6 +134,7 @@ export class ClientUpload { await client.getPartURL( { resourceId: id, + resourceSignature, partNumber, uploadedPartSize: 0n, isInternalUse: false, @@ -141,7 +148,9 @@ export class ClientUpload { await client.POST("/v1/upload/get-part-url", { body: { resourceId: id.toString(), - resourceSignature: "", + resourceSignature: resourceSignature + ? Buffer.from(resourceSignature).toString("base64") + : "", partNumber: partNumber.toString(), uploadedPartSize: "0", isInternalUse: false, @@ -212,7 +221,11 @@ export class ClientUpload { ); } - await this.updateProgress({ id, type }, BigInt(info.chunkEnd - info.chunkStart), options); + await this.updateProgress( + { id, type, resourceSignature }, + BigInt(info.chunkEnd - info.chunkStart), + options, + ); } public async finalize(info: ResourceInfo, options?: RpcOptions) { @@ -222,6 +235,7 @@ export class ClientUpload { await client.finalize( { resourceId: info.id, + resourceSignature: info.resourceSignature, checksumAlgorithm: UploadAPI_ChecksumAlgorithm.UNSPECIFIED, checksum: new Uint8Array(0), }, @@ -231,7 +245,9 @@ export class ClientUpload { await client.POST("/v1/upload/finalize", { body: { resourceId: info.id.toString(), - resourceSignature: "", + resourceSignature: info.resourceSignature + ? Buffer.from(info.resourceSignature).toString("base64") + : "", checksumAlgorithm: 0, checksum: "", }, @@ -254,7 +270,7 @@ export class ClientUpload { } private async updateProgress( - { id, type }: ResourceInfo, + { id, type, resourceSignature }: ResourceInfo, bytesProcessed: bigint, options?: RpcOptions, ): Promise { @@ -264,6 +280,7 @@ export class ClientUpload { await client.updateProgress( { resourceId: id, + resourceSignature, bytesProcessed, }, addRTypeToMetadata(type, options), @@ -274,7 +291,9 @@ export class ClientUpload { await client.POST("/v1/upload/update-progress", { body: { resourceId: id.toString(), - resourceSignature: "", + resourceSignature: resourceSignature + ? Buffer.from(resourceSignature).toString("base64") + : "", bytesProcessed: bytesProcessed.toString(), }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/drivers/download_blob/download_blob.test.ts b/lib/node/pl-drivers/src/drivers/download_blob/download_blob.test.ts index 7cad2f97c3..132121b9eb 100644 --- a/lib/node/pl-drivers/src/drivers/download_blob/download_blob.test.ts +++ b/lib/node/pl-drivers/src/drivers/download_blob/download_blob.test.ts @@ -304,6 +304,7 @@ async function makeDownloadableBlobFromAssets(client: PlClient, fileName: string return { id: download.id, type: download.type, + resourceSignature: download.resourceSignature, data: undefined, fields: undefined, kv: { diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 6f06c6d645..d07a532cb8 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -10,13 +10,16 @@ import { getSize } from "../types"; // https://regex101.com/r/Q4YdTa/5 const remoteHandleRegex = - /^blob\+remote:\/\/download\/(?(?.+)\/(?.+?)\/(?\d+?)\/(?\d+?))#(?.*)$/; + /^blob\+remote:\/\/download\/(?(?.+)\/(?.+?)\/(?\d+?)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?)#(?.*)$/; export function newRemoteHandle( rInfo: OnDemandBlobResourceSnapshot, signer: Signer, ): RemoteBlobHandle { let content = `${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}/${getSize(rInfo)}`; + if (rInfo.resourceSignature) { + content += `/${Buffer.from(rInfo.resourceSignature).toString('base64url')}`; + } return `blob+remote://download/${content}#${signer.sign(content)}` as RemoteBlobHandle; } @@ -37,7 +40,7 @@ export function parseRemoteHandle( throw new Error(`Remote handle is malformed: ${handle}, matches: ${parsed}`); } - const { content, resourceType, resourceVersion, resourceId, size, signature } = parsed.groups!; + const { content, resourceType, resourceVersion, resourceId, size, resourceSig, signature } = parsed.groups!; signer.verify(content, signature, `Signature verification failed for ${handle}`); @@ -45,6 +48,7 @@ export function parseRemoteHandle( info: { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, + ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') } : {}), }, size: Number(size), }; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index f5f4c5f79e..43a4b38adb 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -6,11 +6,14 @@ import type * as sdk from "@milaboratories/pl-model-common"; import { bigintToResourceId } from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { + const resSig = rInfo.resourceSignature + ? `/${Buffer.from(rInfo.resourceSignature).toString('base64url')}` + : ''; if (live) { - return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}` as sdk.LiveLogHandle; + return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.LiveLogHandle; } - return `log+ready://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}` as sdk.ReadyLogHandle; + return `log+ready://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.ReadyLogHandle; } /** Handle of the live logs of a program. @@ -18,7 +21,7 @@ export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHand * in this case the handle should be refreshed. */ export const liveHandleRegex = - /^log\+live:\/\/log\/(?.*)\/(?.*)\/(?.*)$/; + /^log\+live:\/\/log\/(?.*)\/(?.*)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { return liveHandleRegex.test(handle); @@ -27,7 +30,7 @@ export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { /** Handle of the ready logs of a program. */ export const readyHandleRegex = - /^log\+ready:\/\/log\/(?.*)\/(?.*)\/(?.*)$/; + /^log\+ready:\/\/log\/(?.*)\/(?.*)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isReadyLogHandle(handle: string): handle is sdk.ReadyLogHandle { return readyHandleRegex.test(handle); @@ -43,10 +46,11 @@ export function getResourceInfoFromLogHandle(handle: sdk.AnyLogHandle): Resource } else throw new Error(`Log handle is malformed: ${handle}`); if (parsed == null) throw new Error(`Log handle wasn't parsed: ${handle}`); - const { resourceType, resourceVersion, resourceId } = parsed.groups!; + const { resourceType, resourceVersion, resourceId, resourceSig } = parsed.groups!; return { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, + ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') } : {}), }; } diff --git a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts index 40b2a5f424..03b6ffa272 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts @@ -60,6 +60,7 @@ export type RemoteStorageHandleData = { name: string; id: ResourceId; type: ResourceType; + resourceSignature?: Uint8Array; }; const remoteHandleRegex = /^remote:\/\/(?.*)\/(?.*)$/; diff --git a/lib/node/pl-drivers/src/drivers/ls.ts b/lib/node/pl-drivers/src/drivers/ls.ts index a8570e6fb9..a3469114f8 100644 --- a/lib/node/pl-drivers/src/drivers/ls.ts +++ b/lib/node/pl-drivers/src/drivers/ls.ts @@ -1,4 +1,4 @@ -import type { PlClient, ResourceData, ResourceId } from "@milaboratories/pl-client"; +import type { PlClient, ResourceData, ResourceId, SignatureCache } from "@milaboratories/pl-client"; import { isNotNullResourceId } from "@milaboratories/pl-client"; import type * as sdk from "@milaboratories/pl-model-common"; import type { @@ -23,6 +23,7 @@ import { parseUploadHandle, } from "./helpers/ls_remote_import_handle"; import { + type StorageHandleData, createLocalStorageHandle, createRemoteStorageHandle, parseStorageHandle, @@ -73,6 +74,8 @@ export class LsDriver implements InternalLsDriver { /** Local projections by storageId */ private readonly localProjectionsMap: Map, private readonly openFileDialogCallback: OpenFileDialogCallback, + /** Cross-transaction signature cache for resource authorization */ + private readonly signatureCache?: SignatureCache, ) {} public async getLocalFileContent( @@ -209,11 +212,22 @@ export class LsDriver implements InternalLsDriver { return [...virtualStorages, ...noRoot]; } + /** Enrich parsed storage data with resource signature from cache */ + private withSignature(storageData: StorageHandleData): StorageHandleData { + if (storageData.isRemote && this.signatureCache) { + const sig = this.signatureCache.get(storageData.id as bigint); + if (sig) { + return { ...storageData, resourceSignature: sig }; + } + } + return storageData; + } + public async listFiles( storageHandle: sdk.StorageHandle, fullPath: string, ): Promise { - const storageData = parseStorageHandle(storageHandle); + const storageData = this.withSignature(parseStorageHandle(storageHandle)); if (storageData.isRemote) { const response = await this.lsClient.list(storageData, fullPath); @@ -258,7 +272,7 @@ export class LsDriver implements InternalLsDriver { storageHandle: sdk.StorageHandle, fullPath: string, ): Promise { - const storageData = parseStorageHandle(storageHandle); + const storageData = this.withSignature(parseStorageHandle(storageHandle)); if (!storageData.isRemote) { throw new Error(`Storage ${storageData.name} is not remote`); } @@ -320,6 +334,7 @@ export class LsDriver implements InternalLsDriver { virtualStoragesMap, localProjectionsMap, openFileDialogCallback, + client.signatureCache, ); } } diff --git a/lib/node/pl-tree/src/accessors.ts b/lib/node/pl-tree/src/accessors.ts index 04b633c06d..184aaa6d7a 100644 --- a/lib/node/pl-tree/src/accessors.ts +++ b/lib/node/pl-tree/src/accessors.ts @@ -152,6 +152,7 @@ export class PlTreeEntryAccessor { export type ResourceInfo = { readonly id: ResourceId; readonly type: ResourceType; + readonly resourceSignature?: Uint8Array; }; /** @@ -196,7 +197,7 @@ export class PlTreeNodeAccessor { } public get resourceInfo(): ResourceInfo { - return { id: this.id, type: this.resourceType }; + return { id: this.id, type: this.resourceType, resourceSignature: this.resource.resourceSignature }; } private getResourceFromTree(rid: ResourceId, ops: ResourceTraversalOps): PlTreeNodeAccessor { diff --git a/lib/node/pl-tree/src/snapshot.ts b/lib/node/pl-tree/src/snapshot.ts index 9509305db6..ab9358dde1 100644 --- a/lib/node/pl-tree/src/snapshot.ts +++ b/lib/node/pl-tree/src/snapshot.ts @@ -18,6 +18,7 @@ export type ResourceSnapshot< > = { readonly id: ResourceId; readonly type: ResourceType; + readonly resourceSignature?: Uint8Array; readonly data: Data; readonly fields: Fields; readonly kv: KV; diff --git a/lib/node/pl-tree/src/state.ts b/lib/node/pl-tree/src/state.ts index 8067006798..65a1797413 100644 --- a/lib/node/pl-tree/src/state.ts +++ b/lib/node/pl-tree/src/state.ts @@ -105,6 +105,7 @@ export class PlTreeResource implements ResourceDataWithFinalState { readonly type: ResourceType; readonly data?: Uint8Array; + resourceSignature?: Uint8Array; private dataAsString?: string; private dataAsJson?: unknown; @@ -131,6 +132,7 @@ export class PlTreeResource implements ResourceDataWithFinalState { this.outputsLocked = initialState.outputsLocked; this.resourceReady = initialState.resourceReady; this.finalFlag = initialState.final; + this.resourceSignature = initialState.resourceSignature; this.logger = logger; } @@ -343,6 +345,7 @@ export class PlTreeResource implements ResourceDataWithFinalState { error: this.error, originalResourceId: this.originalResourceId, final: this.finalFlag, + resourceSignature: this.resourceSignature, }; } @@ -454,6 +457,9 @@ export class PlTreeState { // updating resource version, even if it was not changed resource.version += 1; + // always update signature to latest from server + resource.resourceSignature = rd.resourceSignature; + // duplicate / original if (resource.originalResourceId !== rd.originalResourceId) { if (resource.originalResourceId !== NullResourceId) From 0702bdc2eec9e9933d4bc4c559f88d63ace94881 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 17:42:27 +0200 Subject: [PATCH 15/27] MILAB-5815: autogen: changeset --- .changeset/cuddly-dancers-greet.md | 92 ------------------------ .changeset/resource-signature-support.md | 11 +++ 2 files changed, 11 insertions(+), 92 deletions(-) delete mode 100644 .changeset/cuddly-dancers-greet.md create mode 100644 .changeset/resource-signature-support.md diff --git a/.changeset/cuddly-dancers-greet.md b/.changeset/cuddly-dancers-greet.md deleted file mode 100644 index 2702dcc77b..0000000000 --- a/.changeset/cuddly-dancers-greet.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -"@milaboratories/pl-client": major ---- - -Sync pl proto API: resource signing, access control, locks, auth, field renames. - -**Breaking changes:** - -Proto field renames: -- `Resource.id` → `Resource.resource_id` -- `ResourceAPI.Remove.Request.id` → `ResourceAPI.Remove.Request.resource_id` -- `FieldAPI.SetError.Request.err_resource_id` → `FieldAPI.SetError.Request.error_resource_id` - -Proto field removals: -- `MaintenanceAPI.Ping.Response.server_info` removed (field 3) - -Proto deprecations: -- `CacheAPI.DeleteExpiredRecords` replaced with `Util.Deprecated` placeholder - -Lease endpoint URL changes: -- `/v1/locks/lease` → `/v1/locks/lease/create` -- `PUT /v1/locks/lease` → `POST /v1/locks/lease/update` -- `DELETE /v1/locks/lease` → `POST /v1/locks/lease/release` - -**New proto fields — resource signing:** - -Core messages: -- `Resource.resource_signature` (bytes) — opaque signature for resource ID -- `Field.value_signature` (bytes) — signature for field value resource, inheriting parent color -- `Field.error_signature` (bytes) — signature for error resource, inheriting parent color -- `FieldRef.resource_signature` (bytes) — signature for the referenced resource - -Transaction operations: -- `TxAPI.SetDefaultColor` — set default color for resource creation via `color_proof` - -Resource creation — `color_proof` added to: -- `CreateStruct.Request`, `CreateEphemeral.Request`, `CreateValue.Request`, `CreateSingleton.Request` - -Resource creation — `resource_signature` added to responses: -- `CreateStruct`, `CreateEphemeral`, `CreateValue`, `CreateSingleton`, `CreateRoot`, `GetValueID` - -Resource access — `resource_signature` added to requests: -- `Remove`, `Get`, `LockInputs`, `LockOutputs`, `Exists`, `SetError` (+ `error_resource_signature`), `Tree`, `TreeSize`, `Name.Set` - -Resource access — `resource_signature` added to responses: -- `Name.Get` - -Field operations — `resource_signature` added to: -- `FieldAPI.List.Request`, `FieldAPI.SetError.Request` (`error_resource_signature`) - -KV operations — `resource_signature` added to all `ResourceKVAPI.*.Request`: -- `Set`, `Get`, `GetIfExists`, `Delete`, `SetFlag`, `GetFlag`, `List` - -Lease operations — `resource_signature` added to: -- `Lease.Create.Request`, `Lease.Update.Request`, `Lease.Release.Request` - -**New API — access control:** - -RPCs: -- `GrantAccess` — grant resource access to another user -- `RevokeGrant` — revoke previously granted access -- `ListGrants` — server-side streaming, list grants for a resource -- `ListUserResources` — server-side streaming, user root + shared resources with pagination - -Messages: -- `AuthAPI.Grant` — grant record (user, resource_id, permissions, granted_by, granted_at) -- `AuthAPI.Grant.Permissions` — access bitmask (writable) -- `AuthAPI.ListUserResources.UserRoot` — signed user root -- `AuthAPI.ListUserResources.SharedResource` — signed shared resource with type and permissions - -**New API — auth:** - -- `AuthAPI.GetJWTToken.Role` enum — `ROLE_UNSPECIFIED`, `USER`, `CONTROLLER`, `WORKFLOW` -- `AuthAPI.GetJWTToken.Request.requested_role` — request JWT with specific role -- `AuthAPI.GetJWTToken.Response.session_id` — 128-bit session ID - -**New API — locks:** - -- `LocksAPI.LockFieldValues` — optimistic locking on resolved field values - -**New API — schema:** - -- `ResourceSchema.AccessFlags` — per-type access restrictions for non-controller roles (create_resource, read_fields, write_fields, read_kv, write_kv, per-field-type overrides via read_by_field_type/write_by_field_type maps) -- `ResourceSchema.free_inputs` / `free_outputs` — skip automatic locking on creation - -**New API — notifications:** - -- `Notification.Events.resource_recovered` — new event type - -**New utility:** - -- `Util.Deprecated` — empty message for deprecated oneOf slots diff --git a/.changeset/resource-signature-support.md b/.changeset/resource-signature-support.md new file mode 100644 index 0000000000..038e5891bf --- /dev/null +++ b/.changeset/resource-signature-support.md @@ -0,0 +1,11 @@ +--- +"@milaboratories/pl-client": minor +"@milaboratories/pl-drivers": minor +"@milaboratories/pl-tree": minor +--- + +Add resource signature propagation for server-side access control. + +- `pl-client`: cross-transaction `SignatureCache`, automatic signature tracking in `PlTransaction` (store/retrieve signatures for all resource and field operations), `setDefaultColor` for color proof on resource creation, `PermissionDeniedError` error type +- `pl-drivers`: pass `resourceSignature` through proxied APIs (download, upload, logs, progress, ls), encode signatures in remote blob and log handles +- `pl-tree`: propagate `resourceSignature` in `ResourceInfo`, `ResourceSnapshot`, and `PlTreeResource` state From 8ac66a7702532612001287e4d56311c82a790c7a Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 17:55:53 +0200 Subject: [PATCH 16/27] MILAB-5815: ref: SignedResourceId --- .../pl-client/src/core/signature_cache.ts | 7 ++ lib/node/pl-client/src/core/transaction.ts | 70 ++++++++----------- lib/node/pl-client/src/core/types.ts | 16 +++++ lib/node/pl-drivers/src/clients/download.ts | 5 +- lib/node/pl-drivers/src/clients/logs.ts | 6 +- lib/node/pl-drivers/src/clients/ls_api.ts | 6 +- lib/node/pl-drivers/src/clients/progress.ts | 6 +- lib/node/pl-drivers/src/clients/upload.ts | 18 ++--- 8 files changed, 66 insertions(+), 68 deletions(-) diff --git a/lib/node/pl-client/src/core/signature_cache.ts b/lib/node/pl-client/src/core/signature_cache.ts index cea37b9520..19d10ce90c 100644 --- a/lib/node/pl-client/src/core/signature_cache.ts +++ b/lib/node/pl-client/src/core/signature_cache.ts @@ -1,3 +1,5 @@ +import type { SignedResourceId } from "./types"; + /** Cross-transaction cache for resource signatures. * Keyed by resource ID (bigint), stores opaque signature bytes (Uint8Array). */ export class SignatureCache { @@ -11,6 +13,11 @@ export class SignatureCache { return this.store.get(id); } + /** Return a SignedResourceId by looking up the cached signature. */ + sign(id: bigint): SignedResourceId { + return { resourceId: id, resourceSignature: this.get(id) }; + } + delete(id: bigint): boolean { return this.store.delete(id); } diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index e526749445..c9b9206de8 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -11,6 +11,8 @@ import type { ResourceId, ResourceType, FutureFieldType, + SignedResourceId, + SignedFieldId, } from "./types"; import { createLocalResourceId, @@ -295,9 +297,12 @@ export class PlTransaction { return this.signatureStore.get(rId) ?? this.sharedSignatureCache?.get(rId); } - private toSignedFieldId( - fId: AnyFieldRef, - ): { resourceId: bigint; fieldName: string; resourceSignature?: Uint8Array } { + private toSignedResourceId(rId: AnyResourceRef): SignedResourceId { + const resourceId = toResourceId(rId); + return { resourceId, resourceSignature: this.getSignature(resourceId) }; + } + + private toSignedFieldId(fId: AnyFieldRef): SignedFieldId { const base = toFieldId(fId); return { ...base, resourceSignature: this.getSignature(base.resourceId) }; } @@ -576,10 +581,9 @@ export class PlTransaction { } public setResourceName(name: string, rId: AnyResourceRef): void { - const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceNameSet", - resourceNameSet: { resourceId: id, resourceSignature: this.getSignature(id), name }, + resourceNameSet: { ...this.toSignedResourceId(rId), name }, }); } @@ -608,7 +612,7 @@ export class PlTransaction { public removeResource(rId: ResourceId): void { this.sendVoidAsync({ oneofKind: "resourceRemove", - resourceRemove: { resourceId: rId, resourceSignature: this.getSignature(rId) }, + resourceRemove: this.toSignedResourceId(rId), }); } @@ -616,7 +620,7 @@ export class PlTransaction { return this.sendSingleAndParse( { oneofKind: "resourceExists", - resourceExists: { resourceId: rId, resourceSignature: this.getSignature(rId) }, + resourceExists: this.toSignedResourceId(rId), }, (r) => r.resourceExists.exists, ); @@ -674,13 +678,11 @@ export class PlTransaction { } } - const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceGet", resourceGet: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), loadFields: loadFields, }, }, @@ -768,10 +770,9 @@ export class PlTransaction { */ public lockInputs(rId: AnyResourceRef): void { this._stat.inputsLocked++; - const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceLockInputs", - resourceLockInputs: { resourceId: id, resourceSignature: this.getSignature(id) }, + resourceLockInputs: this.toSignedResourceId(rId), }); } @@ -781,10 +782,9 @@ export class PlTransaction { */ public lockOutputs(rId: AnyResourceRef): void { this._stat.outputsLocked++; - const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceLockOutputs", - resourceLockOutputs: { resourceId: id, resourceSignature: this.getSignature(id) }, + resourceLockOutputs: this.toSignedResourceId(rId), }); } @@ -794,15 +794,15 @@ export class PlTransaction { } public setResourceError(rId: AnyResourceRef, ref: AnyResourceRef): void { - const id = toResourceId(rId); - const errorId = toResourceId(ref); + const signed = this.toSignedResourceId(rId); + const signedError = this.toSignedResourceId(ref); this.sendVoidAsync({ oneofKind: "resourceSetError", resourceSetError: { - resourceId: id, - resourceSignature: this.getSignature(id), - errorResourceId: errorId, - errorResourceSignature: this.getSignature(errorId), + resourceId: signed.resourceId, + resourceSignature: signed.resourceSignature, + errorResourceId: signedError.resourceId, + errorResourceSignature: signedError.resourceSignature, }, }); } @@ -833,14 +833,12 @@ export class PlTransaction { public setField(fId: AnyFieldRef, ref: AnyRef): void { this._stat.fieldsSet++; if (isResource(ref)) { - const refId = toResourceId(ref); this.sendVoidAsync({ oneofKind: "fieldSet", fieldSet: { field: this.toSignedFieldId(fId), value: { - resourceId: refId, - resourceSignature: this.getSignature(refId), + ...this.toSignedResourceId(ref), fieldName: "", // default value, read as undefined }, }, @@ -858,13 +856,13 @@ export class PlTransaction { public setFieldError(fId: AnyFieldRef, ref: AnyResourceRef): void { this._stat.fieldsSet++; - const errorId = toResourceId(ref); + const signedError = this.toSignedResourceId(ref); this.sendVoidAsync({ oneofKind: "fieldSetError", fieldSetError: { field: this.toSignedFieldId(fId), - errorResourceId: errorId, - errorResourceSignature: this.getSignature(errorId), + errorResourceId: signedError.resourceId, + errorResourceSignature: signedError.resourceSignature, }, }); } @@ -900,13 +898,11 @@ export class PlTransaction { public async listKeyValues(rId: AnyResourceRef): Promise { return this.track(async () => { - const id = toResourceId(rId); const result = await this.sendMultiAndParse( { oneofKind: "resourceKeyValueList", resourceKeyValueList: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), startFrom: "", limit: 0, }, @@ -944,12 +940,10 @@ export class PlTransaction { public setKValue(rId: AnyResourceRef, key: string, value: Uint8Array | string): void { this._stat.kvSetRequests++; this._stat.kvSetBytes++; - const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceKeyValueSet", resourceKeyValueSet: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), key, value: toBytes(value), }, @@ -957,12 +951,10 @@ export class PlTransaction { } public deleteKValue(rId: AnyResourceRef, key: string): void { - const id = toResourceId(rId); this.sendVoidAsync({ oneofKind: "resourceKeyValueDelete", resourceKeyValueDelete: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), key, }, }); @@ -970,13 +962,11 @@ export class PlTransaction { public async getKValue(rId: AnyResourceRef, key: string): Promise { return this.track(async () => { - const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGet", resourceKeyValueGet: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), key, }, }, @@ -1003,13 +993,11 @@ export class PlTransaction { key: string, ): Promise { return this.track(async () => { - const id = toResourceId(rId); const result = await this.sendSingleAndParse( { oneofKind: "resourceKeyValueGetIfExists", resourceKeyValueGetIfExists: { - resourceId: id, - resourceSignature: this.getSignature(id), + ...this.toSignedResourceId(rId), key, }, }, diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index ba7c2b8a6c..4a050890f4 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -68,6 +68,22 @@ export function resourceTypesEqual(type1: ResourceType, type2: ResourceType): bo return type1.name === type2.name && type1.version === type2.version; } +/** Resource ID bundled with its authorization signature. */ +export type SignedResourceId = { + readonly resourceId: bigint; + readonly resourceSignature?: Uint8Array; +}; + +/** Signed field reference — resource signature attached to field's parent resource. */ +export type SignedFieldId = SignedResourceId & { + readonly fieldName: string; +}; + +/** Encode resource signature to base64 string for REST APIs. */ +export function signatureToBase64(sig?: Uint8Array): string { + return sig ? Buffer.from(sig).toString("base64") : ""; +} + /** Readonly fields here marks properties of resource that can't change according to pl's state machine. */ export type BasicResourceData = { readonly id: ResourceId; diff --git a/lib/node/pl-drivers/src/clients/download.ts b/lib/node/pl-drivers/src/clients/download.ts index 732916763e..79107d30a4 100644 --- a/lib/node/pl-drivers/src/clients/download.ts +++ b/lib/node/pl-drivers/src/clients/download.ts @@ -3,6 +3,7 @@ import type { WireClientProvider, WireClientProviderFactory } from "@milaborator import { addRTypeToMetadata, stringifyWithResourceId, + signatureToBase64, RestAPI, createRTypeRoutingHeader, } from "@milaboratories/pl-client"; @@ -147,9 +148,7 @@ export class ClientDownload { await client.POST("/v1/get-download-url", { body: { resourceId: id.toString(), - resourceSignature: resourceSignature - ? Buffer.from(resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(resourceSignature), isInternalUse: false, }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/clients/logs.ts b/lib/node/pl-drivers/src/clients/logs.ts index 34c7326c1e..5487ade1c6 100644 --- a/lib/node/pl-drivers/src/clients/logs.ts +++ b/lib/node/pl-drivers/src/clients/logs.ts @@ -3,7 +3,7 @@ import type { MiLogger } from "@milaboratories/ts-helpers"; import { notEmpty } from "@milaboratories/ts-helpers"; import type { Dispatcher } from "undici"; import type { WireClientProvider, WireClientProviderFactory } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, RestAPI } from "@milaboratories/pl-client"; +import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; import type { StreamingAPI_Response } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol"; import { StreamingClient } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client"; import type { StreamingApiPaths, StreamingRestClientType } from "../proto-rest"; @@ -63,7 +63,7 @@ export class ClientLogs { await client.POST("/v1/last-lines", { body: { resourceId: rId.toString(), - resourceSignature: rSig ? Buffer.from(rSig).toString("base64") : "", + resourceSignature: signatureToBase64(rSig), lineCount: lineCount, offset: offsetBytes.toString(), search: searchStr ?? "", @@ -111,7 +111,7 @@ export class ClientLogs { await client.POST("/v1/read/text", { body: { resourceId: rId.toString(), - resourceSignature: rSig ? Buffer.from(rSig).toString("base64") : "", + resourceSignature: signatureToBase64(rSig), readLimit: lineCount.toString(), offset: offsetBytes.toString(), search: searchStr ?? "", diff --git a/lib/node/pl-drivers/src/clients/ls_api.ts b/lib/node/pl-drivers/src/clients/ls_api.ts index 23a6ae6659..20dd9523b6 100644 --- a/lib/node/pl-drivers/src/clients/ls_api.ts +++ b/lib/node/pl-drivers/src/clients/ls_api.ts @@ -2,7 +2,7 @@ import type { MiLogger } from "@milaboratories/ts-helpers"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; import type { WireClientProvider, WireClientProviderFactory } from "@milaboratories/pl-client"; import { RestAPI } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader } from "@milaboratories/pl-client"; +import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64 } from "@milaboratories/pl-client"; import type { LsAPI_List_Response, LsAPI_ListItem, @@ -56,9 +56,7 @@ export class ClientLs { await client.POST("/v1/list", { body: { resourceId: rInfo.id.toString(), - resourceSignature: rInfo.resourceSignature - ? Buffer.from(rInfo.resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(rInfo.resourceSignature), location: path, }, headers: { ...createRTypeRoutingHeader(rInfo.type) }, diff --git a/lib/node/pl-drivers/src/clients/progress.ts b/lib/node/pl-drivers/src/clients/progress.ts index 478d463063..b5ae221adb 100644 --- a/lib/node/pl-drivers/src/clients/progress.ts +++ b/lib/node/pl-drivers/src/clients/progress.ts @@ -4,7 +4,7 @@ import type { WireClientProviderFactory, PlClient, } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, RestAPI } from "@milaboratories/pl-client"; +import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; import type { MiLogger } from "@milaboratories/ts-helpers"; import { notEmpty } from "@milaboratories/ts-helpers"; import type { Dispatcher } from "undici"; @@ -71,9 +71,7 @@ export class ClientProgress { await client.POST("/v1/get-progress", { body: { resourceId: id.toString(), - resourceSignature: resourceSignature - ? Buffer.from(resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(resourceSignature), }, headers: { ...createRTypeRoutingHeader(type) }, }) diff --git a/lib/node/pl-drivers/src/clients/upload.ts b/lib/node/pl-drivers/src/clients/upload.ts index b58ecdd56f..acc385b235 100644 --- a/lib/node/pl-drivers/src/clients/upload.ts +++ b/lib/node/pl-drivers/src/clients/upload.ts @@ -3,7 +3,7 @@ import type { WireClientProviderFactory, PlClient, } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, RestAPI } from "@milaboratories/pl-client"; +import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; import type { ResourceInfo } from "@milaboratories/pl-tree"; import type { MiLogger } from "@milaboratories/ts-helpers"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; @@ -100,9 +100,7 @@ export class ClientUpload { await client.POST("/v1/upload/init", { body: { resourceId: id.toString(), - resourceSignature: resourceSignature - ? Buffer.from(resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(resourceSignature), }, headers: { ...createRTypeRoutingHeader(type) }, }) @@ -148,9 +146,7 @@ export class ClientUpload { await client.POST("/v1/upload/get-part-url", { body: { resourceId: id.toString(), - resourceSignature: resourceSignature - ? Buffer.from(resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(resourceSignature), partNumber: partNumber.toString(), uploadedPartSize: "0", isInternalUse: false, @@ -245,9 +241,7 @@ export class ClientUpload { await client.POST("/v1/upload/finalize", { body: { resourceId: info.id.toString(), - resourceSignature: info.resourceSignature - ? Buffer.from(info.resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(info.resourceSignature), checksumAlgorithm: 0, checksum: "", }, @@ -291,9 +285,7 @@ export class ClientUpload { await client.POST("/v1/upload/update-progress", { body: { resourceId: id.toString(), - resourceSignature: resourceSignature - ? Buffer.from(resourceSignature).toString("base64") - : "", + resourceSignature: signatureToBase64(resourceSignature), bytesProcessed: bytesProcessed.toString(), }, headers: { ...createRTypeRoutingHeader(type) }, From 84698f36a6babe999a6997b19a901a0c38e8cde4 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 18:15:57 +0200 Subject: [PATCH 17/27] MILAB-5815: feat: LRU cache --- lib/node/pl-client/src/core/client.ts | 7 ++++++- lib/node/pl-client/src/core/signature_cache.ts | 12 ++++++++++-- lib/node/pl-client/src/core/transaction.ts | 5 ++--- lib/node/pl-client/src/core/types.ts | 7 ++++++- .../src/drivers/helpers/download_remote_handle.ts | 7 ++++--- .../pl-drivers/src/drivers/helpers/logs_handle.ts | 11 +++++------ 6 files changed, 33 insertions(+), 16 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index e88d81153c..d106e2fa57 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -12,7 +12,7 @@ import { NullResourceId, } from "./types"; import { ClientRoot } from "../helpers/pl"; -import { isUnimplementedError } from "./errors"; +import { isPermissionDenied, isUnimplementedError } from "./errors"; import type { MiLogger, RetryOptions } from "@milaboratories/ts-helpers"; import { assertNever, createRetryState, nextRetryStateOrError } from "@milaboratories/ts-helpers"; import type { PlDriver, PlDriverDefinition } from "./driver"; @@ -397,6 +397,11 @@ export class PlClient { } else { // collecting stat this._txErrorStat = addStat(this._txErrorStat, tx.stat); + // Invalidate stale signatures on permission denied so the next + // transaction can re-fetch fresh ones from the server. + if (isPermissionDenied(e)) { + this._signatureCache.clear(); + } throw e; } } finally { diff --git a/lib/node/pl-client/src/core/signature_cache.ts b/lib/node/pl-client/src/core/signature_cache.ts index 19d10ce90c..8ef112d4cd 100644 --- a/lib/node/pl-client/src/core/signature_cache.ts +++ b/lib/node/pl-client/src/core/signature_cache.ts @@ -1,9 +1,17 @@ +import { LRUCache } from "lru-cache"; import type { SignedResourceId } from "./types"; +const DEFAULT_MAX_ENTRIES = 100_000; + /** Cross-transaction cache for resource signatures. - * Keyed by resource ID (bigint), stores opaque signature bytes (Uint8Array). */ + * Keyed by resource ID (bigint), stores opaque signature bytes (Uint8Array). + * Uses LRU eviction to prevent unbounded memory growth in long-running clients. */ export class SignatureCache { - private readonly store = new Map(); + private readonly store: LRUCache; + + constructor(maxEntries: number = DEFAULT_MAX_ENTRIES) { + this.store = new LRUCache({ max: maxEntries }); + } set(id: bigint, sig: Uint8Array): void { this.store.set(id, sig); diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index c9b9206de8..1cc426f98b 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -309,9 +309,8 @@ export class PlTransaction { private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { this.storeSignature(result.id, result.resourceSignature); - const rd = result as ResourceData; - if (rd.fields) { - for (const f of rd.fields) { + if ('fields' in result && result.fields) { + for (const f of result.fields) { if (!isNullResourceId(f.value)) this.storeSignature(f.value, f.valueSignature); if (!isNullResourceId(f.error)) this.storeSignature(f.error, f.errorSignature); } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index 4a050890f4..7bf3ec90ba 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -79,11 +79,16 @@ export type SignedFieldId = SignedResourceId & { readonly fieldName: string; }; -/** Encode resource signature to base64 string for REST APIs. */ +/** Encode resource signature to standard base64 for REST API bodies. */ export function signatureToBase64(sig?: Uint8Array): string { return sig ? Buffer.from(sig).toString("base64") : ""; } +/** Encode resource signature to base64url for embedding in URL-based handles. */ +export function signatureToBase64Url(sig?: Uint8Array): string { + return sig ? Buffer.from(sig).toString("base64url") : ""; +} + /** Readonly fields here marks properties of resource that can't change according to pl's state machine. */ export type BasicResourceData = { readonly id: ResourceId; diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index d07a532cb8..266acf3e44 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -4,7 +4,7 @@ import type { Signer } from "@milaboratories/ts-helpers"; import type { OnDemandBlobResourceSnapshot } from "../types"; import type { RemoteBlobHandle } from "@milaboratories/pl-model-common"; -import { bigintToResourceId } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url } from "@milaboratories/pl-client"; import { ResourceInfo } from "@milaboratories/pl-tree"; import { getSize } from "../types"; @@ -17,8 +17,9 @@ export function newRemoteHandle( signer: Signer, ): RemoteBlobHandle { let content = `${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}/${getSize(rInfo)}`; - if (rInfo.resourceSignature) { - content += `/${Buffer.from(rInfo.resourceSignature).toString('base64url')}`; + const sigStr = signatureToBase64Url(rInfo.resourceSignature); + if (sigStr) { + content += `/${sigStr}`; } return `blob+remote://download/${content}#${signer.sign(content)}` as RemoteBlobHandle; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index 43a4b38adb..d89c94d5dd 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -3,12 +3,11 @@ import type { ResourceInfo } from "@milaboratories/pl-tree"; import type * as sdk from "@milaboratories/pl-model-common"; -import { bigintToResourceId } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url } from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { - const resSig = rInfo.resourceSignature - ? `/${Buffer.from(rInfo.resourceSignature).toString('base64url')}` - : ''; + const sigStr = signatureToBase64Url(rInfo.resourceSignature); + const resSig = sigStr ? `/${sigStr}` : ''; if (live) { return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.LiveLogHandle; } @@ -21,7 +20,7 @@ export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHand * in this case the handle should be refreshed. */ export const liveHandleRegex = - /^log\+live:\/\/log\/(?.*)\/(?.*)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+live:\/\/log\/(?[^\/]+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { return liveHandleRegex.test(handle); @@ -30,7 +29,7 @@ export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { /** Handle of the ready logs of a program. */ export const readyHandleRegex = - /^log\+ready:\/\/log\/(?.*)\/(?.*)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+ready:\/\/log\/(?[^\/]+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isReadyLogHandle(handle: string): handle is sdk.ReadyLogHandle { return readyHandleRegex.test(handle); From 392677f495776497eb686dbb9ce9c385c963bbfc Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 18:33:19 +0200 Subject: [PATCH 18/27] MILAB-5815: feat: better types; ResourceSignature --- lib/node/pl-client/src/core/client.ts | 6 ++-- .../pl-client/src/core/signature_cache.ts | 16 ++++----- lib/node/pl-client/src/core/transaction.ts | 34 +++++++++++-------- .../pl-client/src/core/type_conversion.ts | 7 ++-- lib/node/pl-client/src/core/types.ts | 21 ++++++++---- .../drivers/helpers/download_remote_handle.ts | 4 +-- .../src/drivers/helpers/logs_handle.ts | 4 +-- .../src/drivers/helpers/ls_storage_entry.ts | 4 +-- lib/node/pl-drivers/src/drivers/ls.ts | 2 +- lib/node/pl-tree/src/accessors.ts | 4 +-- lib/node/pl-tree/src/snapshot.ts | 4 +-- lib/node/pl-tree/src/state.ts | 3 +- 12 files changed, 62 insertions(+), 47 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index d106e2fa57..5f96c145ba 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -4,7 +4,7 @@ import { LLPlClient } from "./ll_client"; import type { AnyResourceRef } from "./transaction"; import { PlTransaction, toGlobalResourceId, TxCommitConflict } from "./transaction"; import { createHash } from "node:crypto"; -import type { OptionalResourceId, ResourceId } from "./types"; +import type { OptionalResourceId, ResourceId, ResourceSignature } from "./types"; import { bigintToResourceId, ensureResourceIdNotNull, @@ -242,13 +242,13 @@ export class PlClient { // Try ListUserResources first (new backend, gRPC only) let rootFromServer: ResourceId | undefined; - let rootSignature: Uint8Array | undefined; + let rootSignature: ResourceSignature | undefined; try { const responses = await this._ll.listUserResources({ limit: 1 }); for (const msg of responses) { if (msg.entry.oneofKind === "userRoot") { rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId); - rootSignature = msg.entry.userRoot.resourceSignature; + rootSignature = msg.entry.userRoot.resourceSignature as ResourceSignature; break; } } diff --git a/lib/node/pl-client/src/core/signature_cache.ts b/lib/node/pl-client/src/core/signature_cache.ts index 8ef112d4cd..a0852e08a6 100644 --- a/lib/node/pl-client/src/core/signature_cache.ts +++ b/lib/node/pl-client/src/core/signature_cache.ts @@ -1,32 +1,32 @@ import { LRUCache } from "lru-cache"; -import type { SignedResourceId } from "./types"; +import type { ResourceId, ResourceSignature, SignedResourceId } from "./types"; const DEFAULT_MAX_ENTRIES = 100_000; /** Cross-transaction cache for resource signatures. - * Keyed by resource ID (bigint), stores opaque signature bytes (Uint8Array). + * Keyed by ResourceId, stores opaque ResourceSignature bytes. * Uses LRU eviction to prevent unbounded memory growth in long-running clients. */ export class SignatureCache { - private readonly store: LRUCache; + private readonly store: LRUCache; constructor(maxEntries: number = DEFAULT_MAX_ENTRIES) { this.store = new LRUCache({ max: maxEntries }); } - set(id: bigint, sig: Uint8Array): void { + set(id: ResourceId, sig: ResourceSignature): void { this.store.set(id, sig); } - get(id: bigint): Uint8Array | undefined { + get(id: ResourceId): ResourceSignature | undefined { return this.store.get(id); } /** Return a SignedResourceId by looking up the cached signature. */ - sign(id: bigint): SignedResourceId { + sign(id: ResourceId): SignedResourceId { return { resourceId: id, resourceSignature: this.get(id) }; } - delete(id: bigint): boolean { + delete(id: ResourceId): boolean { return this.store.delete(id); } @@ -34,7 +34,7 @@ export class SignatureCache { this.store.clear(); } - has(id: bigint): boolean { + has(id: ResourceId): boolean { return this.store.has(id); } diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 1cc426f98b..98dd33f2c7 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -2,6 +2,7 @@ /* eslint-disable no-prototype-builtins */ import type { AnyResourceId, + ColorProof, LocalResourceId, OptionalResourceId, BasicResourceData, @@ -9,6 +10,7 @@ import type { FieldType, ResourceData, ResourceId, + ResourceSignature, ResourceType, FutureFieldType, SignedResourceId, @@ -168,10 +170,10 @@ export class PlTransaction { private localResourceIdCounter = 0; /** Maps resource ids (both local and global) to their signatures received from the server */ - private readonly signatureStore = new Map(); + private readonly signatureStore = new Map(); /** Default color proof to include in resource creation requests */ - private defaultColorProof?: Uint8Array; + private defaultColorProof?: ColorProof; /** Store logical tx open / closed state to prevent invalid sequence of requests. * True means output stream was completed. @@ -286,15 +288,19 @@ export class PlTransaction { void this.track(this.sendVoidSync(r)); } - private storeSignature(id: bigint, sig?: Uint8Array): void { + private storeSignature(id: AnyResourceId, sig?: Uint8Array): void { if (sig && sig.length > 0) { - this.signatureStore.set(id, sig); - this.sharedSignatureCache?.set(id, sig); + const rs = sig as ResourceSignature; + this.signatureStore.set(id, rs); + if (!isLocalResourceId(id)) { + this.sharedSignatureCache?.set(id, rs); + } } } - private getSignature(rId: bigint): Uint8Array | undefined { - return this.signatureStore.get(rId) ?? this.sharedSignatureCache?.get(rId); + private getSignature(rId: AnyResourceId): ResourceSignature | undefined { + return this.signatureStore.get(rId) + ?? (!isLocalResourceId(rId) ? this.sharedSignatureCache?.get(rId) : undefined); } private toSignedResourceId(rId: AnyResourceRef): SignedResourceId { @@ -318,7 +324,7 @@ export class PlTransaction { } /** Set default color proof for subsequent resource creation requests */ - public setDefaultColor(colorProof: Uint8Array): void { + public setDefaultColor(colorProof: ColorProof): void { this.defaultColorProof = colorProof; this.sendVoidAsync({ oneofKind: "setDefaultColor", @@ -405,7 +411,7 @@ export class PlTransaction { name: string, type: ResourceType, errorIfExists: boolean = false, - colorProof?: Uint8Array, + colorProof?: ColorProof, ): ResourceRef { const localId = this.nextLocalResourceId(false); @@ -422,10 +428,10 @@ export class PlTransaction { }, (r) => { const sig = r.resourceCreateSingleton.resourceSignature; - const id = r.resourceCreateSingleton.resourceId; + const id = r.resourceCreateSingleton.resourceId as ResourceId; this.storeSignature(id, sig); this.storeSignature(localId, sig); - return id as ResourceId; + return id; }, ); @@ -492,7 +498,7 @@ export class PlTransaction { public createStruct( type: ResourceType, data?: Uint8Array | string, - colorProof?: Uint8Array, + colorProof?: ColorProof, ): ResourceRef { this._stat.structsCreated++; this._stat.structsCreatedDataBytes += data?.length ?? 0; @@ -516,7 +522,7 @@ export class PlTransaction { public createEphemeral( type: ResourceType, data?: Uint8Array | string, - colorProof?: Uint8Array, + colorProof?: ColorProof, ): ResourceRef { this._stat.ephemeralsCreated++; this._stat.ephemeralsCreatedDataBytes += data?.length ?? 0; @@ -541,7 +547,7 @@ export class PlTransaction { type: ResourceType, data: Uint8Array | string, errorIfExists: boolean = false, - colorProof?: Uint8Array, + colorProof?: ColorProof, ): ResourceRef { this._stat.valuesCreated++; this._stat.valuesCreatedDataBytes += data?.length ?? 0; diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index cce4cfeb41..c0935da7d6 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -15,6 +15,7 @@ import type { ResourceData, ResourceId, ResourceKind, + ResourceSignature, } from "./types"; import { NullResourceId } from "./types"; import { assertNever, notEmpty } from "@milaboratories/ts-helpers"; @@ -40,7 +41,7 @@ export function protoToResource(proto: Resource): ResourceData { kind: protoToResourceKind(proto.kind), error: protoToError(proto), final: proto.isFinal, - resourceSignature: proto.resourceSignature, + resourceSignature: proto.resourceSignature as ResourceSignature, fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField), }; } @@ -69,8 +70,8 @@ export function protoToField(proto: Field): FieldData { value: proto.value as OptionalResourceId, error: proto.error as OptionalResourceId, valueIsFinal: proto.valueIsFinal, - valueSignature: proto.valueSignature, - errorSignature: proto.errorSignature, + valueSignature: proto.valueSignature as ResourceSignature, + errorSignature: proto.errorSignature as ResourceSignature, }; } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index 7bf3ec90ba..96be0d63d7 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -68,10 +68,17 @@ export function resourceTypesEqual(type1: ResourceType, type2: ResourceType): bo return type1.name === type2.name && type1.version === type2.version; } +/** Opaque authorization signature attached to a resource. */ +declare const __resource_signature_type__: unique symbol; +export type ResourceSignature = Uint8Array & { readonly [__resource_signature_type__]: true }; + +/** Color proof used for resource creation requests (alias for ResourceSignature). */ +export type ColorProof = ResourceSignature; + /** Resource ID bundled with its authorization signature. */ export type SignedResourceId = { - readonly resourceId: bigint; - readonly resourceSignature?: Uint8Array; + readonly resourceId: AnyResourceId; + readonly resourceSignature?: ResourceSignature; }; /** Signed field reference — resource signature attached to field's parent resource. */ @@ -80,12 +87,12 @@ export type SignedFieldId = SignedResourceId & { }; /** Encode resource signature to standard base64 for REST API bodies. */ -export function signatureToBase64(sig?: Uint8Array): string { +export function signatureToBase64(sig?: ResourceSignature): string { return sig ? Buffer.from(sig).toString("base64") : ""; } /** Encode resource signature to base64url for embedding in URL-based handles. */ -export function signatureToBase64Url(sig?: Uint8Array): string { +export function signatureToBase64Url(sig?: ResourceSignature): string { return sig ? Buffer.from(sig).toString("base64url") : ""; } @@ -110,7 +117,7 @@ export type BasicResourceData = { readonly final: boolean; /** Signature for this resource, used for authorization in subsequent requests. */ - readonly resourceSignature?: Uint8Array; + readonly resourceSignature?: ResourceSignature; }; export function extractBasicResourceData(rd: ResourceData): BasicResourceData { @@ -165,9 +172,9 @@ export type FieldData = { readonly valueIsFinal: boolean; /** Signature for the value resource, inheriting parent resource's color. */ - readonly valueSignature?: Uint8Array; + readonly valueSignature?: ResourceSignature; /** Signature for the error resource, inheriting parent resource's color. */ - readonly errorSignature?: Uint8Array; + readonly errorSignature?: ResourceSignature; }; // diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 266acf3e44..127decc038 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -4,7 +4,7 @@ import type { Signer } from "@milaboratories/ts-helpers"; import type { OnDemandBlobResourceSnapshot } from "../types"; import type { RemoteBlobHandle } from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url, type ResourceSignature } from "@milaboratories/pl-client"; import { ResourceInfo } from "@milaboratories/pl-tree"; import { getSize } from "../types"; @@ -49,7 +49,7 @@ export function parseRemoteHandle( info: { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') } : {}), + ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') as unknown as ResourceSignature } : {}), }, size: Number(size), }; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index d89c94d5dd..141209b289 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -3,7 +3,7 @@ import type { ResourceInfo } from "@milaboratories/pl-tree"; import type * as sdk from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url, type ResourceSignature } from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { const sigStr = signatureToBase64Url(rInfo.resourceSignature); @@ -50,6 +50,6 @@ export function getResourceInfoFromLogHandle(handle: sdk.AnyLogHandle): Resource return { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') } : {}), + ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') as unknown as ResourceSignature } : {}), }; } diff --git a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts index 03b6ffa272..0a52f2a803 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts @@ -1,5 +1,5 @@ import type * as sdk from "@milaboratories/pl-model-common"; -import type { ResourceId, ResourceType } from "@milaboratories/pl-client"; +import type { ResourceId, ResourceSignature, ResourceType } from "@milaboratories/pl-client"; import { bigintToResourceId } from "@milaboratories/pl-client"; import { assertNever } from "@milaboratories/ts-helpers"; @@ -60,7 +60,7 @@ export type RemoteStorageHandleData = { name: string; id: ResourceId; type: ResourceType; - resourceSignature?: Uint8Array; + resourceSignature?: ResourceSignature; }; const remoteHandleRegex = /^remote:\/\/(?.*)\/(?.*)$/; diff --git a/lib/node/pl-drivers/src/drivers/ls.ts b/lib/node/pl-drivers/src/drivers/ls.ts index a3469114f8..31e4e38c8d 100644 --- a/lib/node/pl-drivers/src/drivers/ls.ts +++ b/lib/node/pl-drivers/src/drivers/ls.ts @@ -215,7 +215,7 @@ export class LsDriver implements InternalLsDriver { /** Enrich parsed storage data with resource signature from cache */ private withSignature(storageData: StorageHandleData): StorageHandleData { if (storageData.isRemote && this.signatureCache) { - const sig = this.signatureCache.get(storageData.id as bigint); + const sig = this.signatureCache.get(storageData.id); if (sig) { return { ...storageData, resourceSignature: sig }; } diff --git a/lib/node/pl-tree/src/accessors.ts b/lib/node/pl-tree/src/accessors.ts index 184aaa6d7a..9fe6d85103 100644 --- a/lib/node/pl-tree/src/accessors.ts +++ b/lib/node/pl-tree/src/accessors.ts @@ -5,7 +5,7 @@ import type { ComputableHooks, UsageGuard, } from "@milaboratories/computable"; -import type { ResourceId, ResourceType, OptionalResourceId } from "@milaboratories/pl-client"; +import type { ResourceId, ResourceSignature, ResourceType, OptionalResourceId } from "@milaboratories/pl-client"; import { resourceIdToString, resourceTypesEqual, @@ -152,7 +152,7 @@ export class PlTreeEntryAccessor { export type ResourceInfo = { readonly id: ResourceId; readonly type: ResourceType; - readonly resourceSignature?: Uint8Array; + readonly resourceSignature?: ResourceSignature; }; /** diff --git a/lib/node/pl-tree/src/snapshot.ts b/lib/node/pl-tree/src/snapshot.ts index ab9358dde1..69905249b9 100644 --- a/lib/node/pl-tree/src/snapshot.ts +++ b/lib/node/pl-tree/src/snapshot.ts @@ -1,4 +1,4 @@ -import type { ResourceId, ResourceType } from "@milaboratories/pl-client"; +import type { ResourceId, ResourceSignature, ResourceType } from "@milaboratories/pl-client"; import type { Optional, Writable } from "utility-types"; import type { ZodType, z } from "zod"; import type { PlTreeNodeAccessor } from "./accessors"; @@ -18,7 +18,7 @@ export type ResourceSnapshot< > = { readonly id: ResourceId; readonly type: ResourceType; - readonly resourceSignature?: Uint8Array; + readonly resourceSignature?: ResourceSignature; readonly data: Data; readonly fields: Fields; readonly kv: KV; diff --git a/lib/node/pl-tree/src/state.ts b/lib/node/pl-tree/src/state.ts index 65a1797413..ac5b7ad667 100644 --- a/lib/node/pl-tree/src/state.ts +++ b/lib/node/pl-tree/src/state.ts @@ -8,6 +8,7 @@ import type { ResourceData, ResourceId, ResourceKind, + ResourceSignature, ResourceType, } from "@milaboratories/pl-client"; import { @@ -105,7 +106,7 @@ export class PlTreeResource implements ResourceDataWithFinalState { readonly type: ResourceType; readonly data?: Uint8Array; - resourceSignature?: Uint8Array; + resourceSignature?: ResourceSignature; private dataAsString?: string; private dataAsJson?: unknown; From 5322110effbdd11a69659c800457c65fb69ef309 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 19:17:00 +0200 Subject: [PATCH 19/27] MILAB-5815: style: format; fix test --- lib/node/pl-client/src/core/client.ts | 24 +++--- lib/node/pl-client/src/core/errors.ts | 4 +- lib/node/pl-client/src/core/transaction.ts | 81 +++++++++++-------- .../pl-client/src/core/type_conversion.ts | 9 +-- lib/node/pl-client/src/core/types.ts | 10 +++ .../drivers/helpers/download_remote_handle.ts | 7 +- .../src/drivers/helpers/logs_handle.ts | 8 +- 7 files changed, 82 insertions(+), 61 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index 5f96c145ba..02e23568f2 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -10,6 +10,7 @@ import { ensureResourceIdNotNull, isNullResourceId, NullResourceId, + toResourceSignature, } from "./types"; import { ClientRoot } from "../helpers/pl"; import { isPermissionDenied, isUnimplementedError } from "./errors"; @@ -248,7 +249,7 @@ export class PlClient { for (const msg of responses) { if (msg.entry.oneofKind === "userRoot") { rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId); - rootSignature = msg.entry.userRoot.resourceSignature as ResourceSignature; + rootSignature = toResourceSignature(msg.entry.userRoot.resourceSignature); break; } } @@ -359,15 +360,11 @@ export class PlClient { // opening low-level tx const llTx = this.ll.createTx(writable, ops); // wrapping it into high-level tx (this also asynchronously sends initialization message) - const tx = new PlTransaction( - llTx, - name, - writable, - clientRoot, - this.finalPredicate, - this.resourceDataCache, - this._signatureCache, - ); + const tx = new PlTransaction(llTx, name, writable, clientRoot, { + finalPredicate: this.finalPredicate, + resourceDataCache: this.resourceDataCache, + signatureCache: this._signatureCache, + }); // Auto-set default color proof so that resource creation (write TXs) // and name lookups (read TXs) carry the correct access color. @@ -397,8 +394,11 @@ export class PlClient { } else { // collecting stat this._txErrorStat = addStat(this._txErrorStat, tx.stat); - // Invalidate stale signatures on permission denied so the next - // transaction can re-fetch fresh ones from the server. + // Invalidate all cached signatures on permission denied. + // Targeted invalidation is impractical here because the failing + // resource id is not reliably available from the error. A full + // clear is safe: signatures are re-populated lazily from server + // responses in subsequent transactions. if (isPermissionDenied(e)) { this._signatureCache.clear(); } diff --git a/lib/node/pl-client/src/core/errors.ts b/lib/node/pl-client/src/core/errors.ts index 3eace214a9..39f1f1e52b 100644 --- a/lib/node/pl-client/src/core/errors.ts +++ b/lib/node/pl-client/src/core/errors.ts @@ -29,8 +29,8 @@ export function isPermissionDenied(err: unknown, nested: boolean = false): boole if (err === undefined || err === null) return false; if (err instanceof PermissionDeniedError) return true; - if ((err as any).name == "RpcError" && (err as any).code == "PERMISSION_DENIED") return true; - if ((err as any).name == "RESTError" && (err as any).status.code == Code.PERMISSION_DENIED) + if ((err as any).name === "RpcError" && (err as any).code === "PERMISSION_DENIED") return true; + if ((err as any).name === "RESTError" && (err as any).status.code === Code.PERMISSION_DENIED) return true; if ((err as any).cause !== undefined && !nested) return isPermissionDenied((err as any).cause, true); diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 98dd33f2c7..d3f0fe123c 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -23,6 +23,7 @@ import { isLocalResourceId, extractBasicResourceData, isNullResourceId, + toResourceSignature, } from "./types"; import type { ClientMessageRequest, @@ -152,6 +153,13 @@ async function notFoundToUndefined(cb: () => Promise): Promise; + signatureCache?: SignatureCache; + enableFormattedErrors?: boolean; +}; + /** * Each platform transaction has 3 stages: * - initialization (txOpen message -> txInfo response) @@ -193,23 +201,30 @@ export class PlTransaction { }; } + private readonly finalPredicate: FinalResourceDataPredicate; + private readonly sharedResourceDataCache: LRUCache; + private readonly sharedSignatureCache?: SignatureCache; + private readonly enableFormattedErrors: boolean; + constructor( private readonly ll: LLPlTransaction, public readonly name: string, public readonly writable: boolean, private readonly _clientRoot: OptionalResourceId, - private readonly finalPredicate: FinalResourceDataPredicate, - private readonly sharedResourceDataCache: LRUCache, - private readonly sharedSignatureCache?: SignatureCache, - private readonly enableFormattedErrors: boolean = false, + options: PlTransactionOptions, ) { + this.finalPredicate = options.finalPredicate; + this.sharedResourceDataCache = options.resourceDataCache; + this.sharedSignatureCache = options.signatureCache; + this.enableFormattedErrors = options.enableFormattedErrors ?? false; + // initiating transaction this.globalTxId = this.sendSingleAndParse( { oneofKind: "txOpen", txOpen: { name, - enableFormattedErrors, + enableFormattedErrors: this.enableFormattedErrors, writable: writable ? TxAPI_Open_Request_WritableTx.WRITABLE : TxAPI_Open_Request_WritableTx.NOT_WRITABLE, @@ -289,8 +304,8 @@ export class PlTransaction { } private storeSignature(id: AnyResourceId, sig?: Uint8Array): void { - if (sig && sig.length > 0) { - const rs = sig as ResourceSignature; + const rs = toResourceSignature(sig); + if (rs && !this.signatureStore.has(id)) { this.signatureStore.set(id, rs); if (!isLocalResourceId(id)) { this.sharedSignatureCache?.set(id, rs); @@ -313,6 +328,14 @@ export class PlTransaction { return { ...base, resourceSignature: this.getSignature(base.resourceId) }; } + private toSignedErrorRef(rId: AnyResourceRef): { + errorResourceId: AnyResourceId; + errorResourceSignature?: ResourceSignature; + } { + const signed = this.toSignedResourceId(rId); + return { errorResourceId: signed.resourceId, errorResourceSignature: signed.resourceSignature }; + } + private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { this.storeSignature(result.id, result.resourceSignature); if ('fields' in result && result.fields) { @@ -413,10 +436,9 @@ export class PlTransaction { errorIfExists: boolean = false, colorProof?: ColorProof, ): ResourceRef { - const localId = this.nextLocalResourceId(false); - - const globalId = this.sendSingleAndParse( - { + return this.createResource( + false, + (localId) => ({ oneofKind: "resourceCreateSingleton", resourceCreateSingleton: { type, @@ -425,19 +447,10 @@ export class PlTransaction { errorIfExists, colorProof: colorProof ?? this.defaultColorProof, }, - }, - (r) => { - const sig = r.resourceCreateSingleton.resourceSignature; - const id = r.resourceCreateSingleton.resourceId as ResourceId; - this.storeSignature(id, sig); - this.storeSignature(localId, sig); - return id; - }, + }), + (r) => r.resourceCreateSingleton.resourceId, + (r) => r.resourceCreateSingleton.resourceSignature, ); - - void this.track(globalId); - - return { globalId, localId }; } public getSingleton(name: string, loadFields: true): Promise; @@ -799,15 +812,11 @@ export class PlTransaction { } public setResourceError(rId: AnyResourceRef, ref: AnyResourceRef): void { - const signed = this.toSignedResourceId(rId); - const signedError = this.toSignedResourceId(ref); this.sendVoidAsync({ oneofKind: "resourceSetError", resourceSetError: { - resourceId: signed.resourceId, - resourceSignature: signed.resourceSignature, - errorResourceId: signedError.resourceId, - errorResourceSignature: signedError.resourceSignature, + ...this.toSignedResourceId(rId), + ...this.toSignedErrorRef(ref), }, }); } @@ -861,13 +870,11 @@ export class PlTransaction { public setFieldError(fId: AnyFieldRef, ref: AnyResourceRef): void { this._stat.fieldsSet++; - const signedError = this.toSignedResourceId(ref); this.sendVoidAsync({ oneofKind: "fieldSetError", fieldSetError: { field: this.toSignedFieldId(fId), - errorResourceId: signedError.resourceId, - errorResourceSignature: signedError.resourceSignature, + ...this.toSignedErrorRef(ref), }, }); } @@ -890,11 +897,17 @@ export class PlTransaction { } public resetField(fId: AnyFieldRef): void { - this.sendVoidAsync({ oneofKind: "fieldReset", fieldReset: { field: this.toSignedFieldId(fId) } }); + this.sendVoidAsync({ + oneofKind: "fieldReset", + fieldReset: { field: this.toSignedFieldId(fId) }, + }); } public removeField(fId: AnyFieldRef): void { - this.sendVoidAsync({ oneofKind: "fieldRemove", fieldRemove: { field: this.toSignedFieldId(fId) } }); + this.sendVoidAsync({ + oneofKind: "fieldRemove", + fieldRemove: { field: this.toSignedFieldId(fId) }, + }); } // diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index c0935da7d6..8508f3a783 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -15,9 +15,8 @@ import type { ResourceData, ResourceId, ResourceKind, - ResourceSignature, } from "./types"; -import { NullResourceId } from "./types"; +import { NullResourceId, toResourceSignature } from "./types"; import { assertNever, notEmpty } from "@milaboratories/ts-helpers"; import { throwPlNotFoundError } from "./errors"; @@ -41,7 +40,7 @@ export function protoToResource(proto: Resource): ResourceData { kind: protoToResourceKind(proto.kind), error: protoToError(proto), final: proto.isFinal, - resourceSignature: proto.resourceSignature as ResourceSignature, + resourceSignature: toResourceSignature(proto.resourceSignature), fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField), }; } @@ -70,8 +69,8 @@ export function protoToField(proto: Field): FieldData { value: proto.value as OptionalResourceId, error: proto.error as OptionalResourceId, valueIsFinal: proto.valueIsFinal, - valueSignature: proto.valueSignature as ResourceSignature, - errorSignature: proto.errorSignature as ResourceSignature, + valueSignature: toResourceSignature(proto.valueSignature), + errorSignature: toResourceSignature(proto.errorSignature), }; } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index 96be0d63d7..878b852bda 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -96,6 +96,16 @@ export function signatureToBase64Url(sig?: ResourceSignature): string { return sig ? Buffer.from(sig).toString("base64url") : ""; } +/** Cast raw bytes to a branded ResourceSignature, returning undefined for empty/missing input. */ +export function toResourceSignature(raw?: Uint8Array): ResourceSignature | undefined { + return raw && raw.length > 0 ? (raw as ResourceSignature) : undefined; +} + +/** Decode base64url-encoded string back to a branded ResourceSignature. */ +export function base64UrlToSignature(str: string): ResourceSignature { + return toResourceSignature(Buffer.from(str, "base64url"))!; +} + /** Readonly fields here marks properties of resource that can't change according to pl's state machine. */ export type BasicResourceData = { readonly id: ResourceId; diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 127decc038..545019a9d5 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -4,13 +4,12 @@ import type { Signer } from "@milaboratories/ts-helpers"; import type { OnDemandBlobResourceSnapshot } from "../types"; import type { RemoteBlobHandle } from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url, type ResourceSignature } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url, base64UrlToSignature } from "@milaboratories/pl-client"; import { ResourceInfo } from "@milaboratories/pl-tree"; import { getSize } from "../types"; -// https://regex101.com/r/Q4YdTa/5 const remoteHandleRegex = - /^blob\+remote:\/\/download\/(?(?.+)\/(?.+?)\/(?\d+?)\/(?\d+?)(?:\/(?[A-Za-z0-9_-]*))?)#(?.*)$/; + /^blob\+remote:\/\/download\/(?(?.+)\/(?[^\/]+)\/(?\d+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?)#(?.*)$/; export function newRemoteHandle( rInfo: OnDemandBlobResourceSnapshot, @@ -49,7 +48,7 @@ export function parseRemoteHandle( info: { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') as unknown as ResourceSignature } : {}), + ...(resourceSig ? { resourceSignature: base64UrlToSignature(resourceSig) } : {}), }, size: Number(size), }; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index 141209b289..85ada56c09 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -3,7 +3,7 @@ import type { ResourceInfo } from "@milaboratories/pl-tree"; import type * as sdk from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url, type ResourceSignature } from "@milaboratories/pl-client"; +import { bigintToResourceId, signatureToBase64Url, base64UrlToSignature } from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { const sigStr = signatureToBase64Url(rInfo.resourceSignature); @@ -20,7 +20,7 @@ export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHand * in this case the handle should be refreshed. */ export const liveHandleRegex = - /^log\+live:\/\/log\/(?[^\/]+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+live:\/\/log\/(?.+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { return liveHandleRegex.test(handle); @@ -29,7 +29,7 @@ export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { /** Handle of the ready logs of a program. */ export const readyHandleRegex = - /^log\+ready:\/\/log\/(?[^\/]+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+ready:\/\/log\/(?.+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isReadyLogHandle(handle: string): handle is sdk.ReadyLogHandle { return readyHandleRegex.test(handle); @@ -50,6 +50,6 @@ export function getResourceInfoFromLogHandle(handle: sdk.AnyLogHandle): Resource return { id: bigintToResourceId(BigInt(resourceId)), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: Buffer.from(resourceSig, 'base64url') as unknown as ResourceSignature } : {}), + ...(resourceSig ? { resourceSignature: base64UrlToSignature(resourceSig) } : {}), }; } From 1cd84e10acddd177f54d7e50e0f8413d3a51876d Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 19:36:36 +0200 Subject: [PATCH 20/27] MILAB-5815: style: fix formatting in pl-tree accessors --- lib/node/pl-tree/src/accessors.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/node/pl-tree/src/accessors.ts b/lib/node/pl-tree/src/accessors.ts index 9fe6d85103..6fbcbebc63 100644 --- a/lib/node/pl-tree/src/accessors.ts +++ b/lib/node/pl-tree/src/accessors.ts @@ -5,7 +5,12 @@ import type { ComputableHooks, UsageGuard, } from "@milaboratories/computable"; -import type { ResourceId, ResourceSignature, ResourceType, OptionalResourceId } from "@milaboratories/pl-client"; +import type { + ResourceId, + ResourceSignature, + ResourceType, + OptionalResourceId, +} from "@milaboratories/pl-client"; import { resourceIdToString, resourceTypesEqual, @@ -197,7 +202,11 @@ export class PlTreeNodeAccessor { } public get resourceInfo(): ResourceInfo { - return { id: this.id, type: this.resourceType, resourceSignature: this.resource.resourceSignature }; + return { + id: this.id, + type: this.resourceType, + resourceSignature: this.resource.resourceSignature, + }; } private getResourceFromTree(rid: ResourceId, ops: ResourceTraversalOps): PlTreeNodeAccessor { From 5aa69af1f0698d6ce50464007ae1a3f3afe93079 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Wed, 8 Apr 2026 23:56:38 +0200 Subject: [PATCH 21/27] MILAB-5815: style: format linter --- lib/node/pl-client/src/core/transaction.ts | 8 +++++--- lib/node/pl-drivers/proto/shared/protodep.toml | 4 ++-- lib/node/pl-drivers/src/clients/logs.ts | 7 ++++++- lib/node/pl-drivers/src/clients/ls_api.ts | 6 +++++- lib/node/pl-drivers/src/clients/progress.ts | 7 ++++++- lib/node/pl-drivers/src/clients/upload.ts | 12 +++++++----- .../src/drivers/helpers/download_remote_handle.ts | 9 +++++++-- .../pl-drivers/src/drivers/helpers/logs_handle.ts | 8 ++++++-- 8 files changed, 44 insertions(+), 17 deletions(-) diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index d3f0fe123c..2c2207417b 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -314,8 +314,10 @@ export class PlTransaction { } private getSignature(rId: AnyResourceId): ResourceSignature | undefined { - return this.signatureStore.get(rId) - ?? (!isLocalResourceId(rId) ? this.sharedSignatureCache?.get(rId) : undefined); + return ( + this.signatureStore.get(rId) ?? + (!isLocalResourceId(rId) ? this.sharedSignatureCache?.get(rId) : undefined) + ); } private toSignedResourceId(rId: AnyResourceRef): SignedResourceId { @@ -338,7 +340,7 @@ export class PlTransaction { private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { this.storeSignature(result.id, result.resourceSignature); - if ('fields' in result && result.fields) { + if ("fields" in result && result.fields) { for (const f of result.fields) { if (!isNullResourceId(f.value)) this.storeSignature(f.value, f.valueSignature); if (!isNullResourceId(f.error)) this.storeSignature(f.error, f.errorSignature); diff --git a/lib/node/pl-drivers/proto/shared/protodep.toml b/lib/node/pl-drivers/proto/shared/protodep.toml index 5974d92a4d..ad71c2f13e 100644 --- a/lib/node/pl-drivers/proto/shared/protodep.toml +++ b/lib/node/pl-drivers/proto/shared/protodep.toml @@ -1,5 +1,5 @@ proto_outdir = "./.proto" [[dependencies]] - target = "github.com/googleapis/googleapis" - path = "./" +target = "github.com/googleapis/googleapis" +path = "./" diff --git a/lib/node/pl-drivers/src/clients/logs.ts b/lib/node/pl-drivers/src/clients/logs.ts index 5487ade1c6..a907ac5718 100644 --- a/lib/node/pl-drivers/src/clients/logs.ts +++ b/lib/node/pl-drivers/src/clients/logs.ts @@ -3,7 +3,12 @@ import type { MiLogger } from "@milaboratories/ts-helpers"; import { notEmpty } from "@milaboratories/ts-helpers"; import type { Dispatcher } from "undici"; import type { WireClientProvider, WireClientProviderFactory } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; +import { + addRTypeToMetadata, + createRTypeRoutingHeader, + signatureToBase64, + RestAPI, +} from "@milaboratories/pl-client"; import type { StreamingAPI_Response } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol"; import { StreamingClient } from "../proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/streamingapi/protocol.client"; import type { StreamingApiPaths, StreamingRestClientType } from "../proto-rest"; diff --git a/lib/node/pl-drivers/src/clients/ls_api.ts b/lib/node/pl-drivers/src/clients/ls_api.ts index 20dd9523b6..cf0c936512 100644 --- a/lib/node/pl-drivers/src/clients/ls_api.ts +++ b/lib/node/pl-drivers/src/clients/ls_api.ts @@ -2,7 +2,11 @@ import type { MiLogger } from "@milaboratories/ts-helpers"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; import type { WireClientProvider, WireClientProviderFactory } from "@milaboratories/pl-client"; import { RestAPI } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64 } from "@milaboratories/pl-client"; +import { + addRTypeToMetadata, + createRTypeRoutingHeader, + signatureToBase64, +} from "@milaboratories/pl-client"; import type { LsAPI_List_Response, LsAPI_ListItem, diff --git a/lib/node/pl-drivers/src/clients/progress.ts b/lib/node/pl-drivers/src/clients/progress.ts index b5ae221adb..3cd9050747 100644 --- a/lib/node/pl-drivers/src/clients/progress.ts +++ b/lib/node/pl-drivers/src/clients/progress.ts @@ -4,7 +4,12 @@ import type { WireClientProviderFactory, PlClient, } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; +import { + addRTypeToMetadata, + createRTypeRoutingHeader, + signatureToBase64, + RestAPI, +} from "@milaboratories/pl-client"; import type { MiLogger } from "@milaboratories/ts-helpers"; import { notEmpty } from "@milaboratories/ts-helpers"; import type { Dispatcher } from "undici"; diff --git a/lib/node/pl-drivers/src/clients/upload.ts b/lib/node/pl-drivers/src/clients/upload.ts index acc385b235..2ec0fd666e 100644 --- a/lib/node/pl-drivers/src/clients/upload.ts +++ b/lib/node/pl-drivers/src/clients/upload.ts @@ -3,7 +3,12 @@ import type { WireClientProviderFactory, PlClient, } from "@milaboratories/pl-client"; -import { addRTypeToMetadata, createRTypeRoutingHeader, signatureToBase64, RestAPI } from "@milaboratories/pl-client"; +import { + addRTypeToMetadata, + createRTypeRoutingHeader, + signatureToBase64, + RestAPI, +} from "@milaboratories/pl-client"; import type { ResourceInfo } from "@milaboratories/pl-tree"; import type { MiLogger } from "@milaboratories/ts-helpers"; import type { RpcOptions } from "@protobuf-ts/runtime-rpc"; @@ -82,10 +87,7 @@ export class ClientUpload { if (client instanceof UploadClient) { const init = ( - await client.init( - { resourceId: id, resourceSignature }, - addRTypeToMetadata(type, options), - ) + await client.init({ resourceId: id, resourceSignature }, addRTypeToMetadata(type, options)) ).response; return { diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 545019a9d5..ad98c45852 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -4,7 +4,11 @@ import type { Signer } from "@milaboratories/ts-helpers"; import type { OnDemandBlobResourceSnapshot } from "../types"; import type { RemoteBlobHandle } from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url, base64UrlToSignature } from "@milaboratories/pl-client"; +import { + bigintToResourceId, + signatureToBase64Url, + base64UrlToSignature, +} from "@milaboratories/pl-client"; import { ResourceInfo } from "@milaboratories/pl-tree"; import { getSize } from "../types"; @@ -40,7 +44,8 @@ export function parseRemoteHandle( throw new Error(`Remote handle is malformed: ${handle}, matches: ${parsed}`); } - const { content, resourceType, resourceVersion, resourceId, size, resourceSig, signature } = parsed.groups!; + const { content, resourceType, resourceVersion, resourceId, size, resourceSig, signature } = + parsed.groups!; signer.verify(content, signature, `Signature verification failed for ${handle}`); diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index 85ada56c09..695c5ff4ce 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -3,11 +3,15 @@ import type { ResourceInfo } from "@milaboratories/pl-tree"; import type * as sdk from "@milaboratories/pl-model-common"; -import { bigintToResourceId, signatureToBase64Url, base64UrlToSignature } from "@milaboratories/pl-client"; +import { + bigintToResourceId, + signatureToBase64Url, + base64UrlToSignature, +} from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { const sigStr = signatureToBase64Url(rInfo.resourceSignature); - const resSig = sigStr ? `/${sigStr}` : ''; + const resSig = sigStr ? `/${sigStr}` : ""; if (live) { return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.LiveLogHandle; } From 8bd1835c22fe1a5c6718805440d7e94b49cbd601 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 09:30:19 +0200 Subject: [PATCH 22/27] MILAB-5815: fix: remove useless regex escapes in pl-drivers handle patterns --- .../pl-drivers/src/drivers/helpers/download_remote_handle.ts | 2 +- lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index ad98c45852..462d12ff9f 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -13,7 +13,7 @@ import { ResourceInfo } from "@milaboratories/pl-tree"; import { getSize } from "../types"; const remoteHandleRegex = - /^blob\+remote:\/\/download\/(?(?.+)\/(?[^\/]+)\/(?\d+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?)#(?.*)$/; + /^blob\+remote:\/\/download\/(?(?.+)\/(?[^/]+)\/(?\d+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?)#(?.*)$/; export function newRemoteHandle( rInfo: OnDemandBlobResourceSnapshot, diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index 695c5ff4ce..5dde5e9a5d 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -24,7 +24,7 @@ export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHand * in this case the handle should be refreshed. */ export const liveHandleRegex = - /^log\+live:\/\/log\/(?.+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+live:\/\/log\/(?.+)\/(?[^/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { return liveHandleRegex.test(handle); @@ -33,7 +33,7 @@ export function isLiveLogHandle(handle: string): handle is sdk.LiveLogHandle { /** Handle of the ready logs of a program. */ export const readyHandleRegex = - /^log\+ready:\/\/log\/(?.+)\/(?[^\/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; + /^log\+ready:\/\/log\/(?.+)\/(?[^/]+)\/(?\d+)(?:\/(?[A-Za-z0-9_-]*))?$/; export function isReadyLogHandle(handle: string): handle is sdk.ReadyLogHandle { return readyHandleRegex.test(handle); From 0a2e14a2473e1d2fbcd4ac25819de60c179dfa3c Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 10:04:23 +0200 Subject: [PATCH 23/27] MILAB-5815: fix: prevent infinite retries when download is aborted but client is already closed When the abort signal fires after the HTTP client has been destroyed, undici throws ClientDestroyedError instead of URLAborted. The task processor treated this as a recoverable error and retried indefinitely, flooding logs and causing EnvironmentTeardownError in vitest tests. Now any error thrown while the abort signal is already set is treated as an abort rather than re-thrown for retry. --- lib/node/pl-drivers/src/drivers/download_url/task.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/node/pl-drivers/src/drivers/download_url/task.ts b/lib/node/pl-drivers/src/drivers/download_url/task.ts index d47334281f..e24fc57fff 100644 --- a/lib/node/pl-drivers/src/drivers/download_url/task.ts +++ b/lib/node/pl-drivers/src/drivers/download_url/task.ts @@ -64,6 +64,16 @@ export class DownloadByUrlTask { return; } + // If our abort was triggered, treat any resulting error (e.g. ClientDestroyedError + // thrown when the client is closed before the abort signal propagates to undici) + // as an abort rather than a recoverable error that would cause infinite retries. + if (this.signalCtl.signal.aborted) { + this.setError(this.signalCtl.signal.reason); + this.change.markChanged(`download of ${this.url} aborted`); + await rmRFDir(this.path); + return; + } + throw e; } } From 7a37723893f1e8e2ebff8dd321dc13f444e981b8 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 13:34:48 +0200 Subject: [PATCH 24/27] MILAB-5815: fix: drop shared signatue cache [drafr] --- lib/node/pl-client/src/core/client.ts | 40 +++++---------- lib/node/pl-client/src/core/ll_client.test.ts | 5 +- .../pl-client/src/core/signature_cache.ts | 44 ---------------- lib/node/pl-client/src/core/transaction.ts | 50 +++++++++---------- lib/node/pl-client/src/core/types.ts | 19 +++---- lib/node/pl-client/src/index.ts | 1 - lib/node/pl-drivers/src/drivers/ls.ts | 44 ++++++++-------- lib/node/pl-tree/src/state.ts | 9 ++-- lib/node/pl-tree/src/synchronized_tree.ts | 8 +-- 9 files changed, 75 insertions(+), 145 deletions(-) delete mode 100644 lib/node/pl-client/src/core/signature_cache.ts diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index 02e23568f2..097ebd6d06 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -1,7 +1,7 @@ import type { AuthOps, PlClientConfig, PlConnectionStatusListener, wireProtocol } from "./config"; import type { PlCallOps } from "./ll_client"; import { LLPlClient } from "./ll_client"; -import type { AnyResourceRef } from "./transaction"; +import type { AnyResourceRef, SignatureResolver } from "./transaction"; import { PlTransaction, toGlobalResourceId, TxCommitConflict } from "./transaction"; import { createHash } from "node:crypto"; import type { OptionalResourceId, ResourceId, ResourceSignature } from "./types"; @@ -13,7 +13,7 @@ import { toResourceSignature, } from "./types"; import { ClientRoot } from "../helpers/pl"; -import { isPermissionDenied, isUnimplementedError } from "./errors"; +import { isUnimplementedError } from "./errors"; import type { MiLogger, RetryOptions } from "@milaboratories/ts-helpers"; import { assertNever, createRetryState, nextRetryStateOrError } from "@milaboratories/ts-helpers"; import type { PlDriver, PlDriverDefinition } from "./driver"; @@ -33,13 +33,13 @@ import { addStat, initialTxStat } from "./stat"; import type { WireConnection } from "./wire"; import { advisoryLock } from "./advisory_locks"; import { plAddressToConfig } from "./config"; -import { SignatureCache } from "./signature_cache"; export type TxOps = PlCallOps & { sync?: boolean; retryOptions?: RetryOptions; name?: string; lockId?: string; + signatureResolver?: SignatureResolver; }; const defaultTxOps = { @@ -98,8 +98,8 @@ export class PlClient { /** Resource data cache, to minimize redundant data rereading from remote db */ private readonly resourceDataCache: LRUCache; - /** Cross-transaction signature cache */ - private readonly _signatureCache = new SignatureCache(); + /** Root signature (user's actual root, used as default color proof) */ + private _rootSignature?: ResourceSignature; private constructor( configOrAddress: PlClientConfig | string, @@ -214,12 +214,6 @@ export class PlClient { return this._serverInfo!; } - /** Shared signature cache, persists across transactions. - * Call clear() on auth errors to invalidate stale signatures. */ - public get signatureCache(): SignatureCache { - return this._signatureCache; - } - /** Discovers or creates the user's root resource. * Tries ListUserResources RPC first (new backend), falls back to * legacy named-resource lookup for older backends. */ @@ -259,10 +253,8 @@ export class PlClient { } if (rootFromServer !== undefined) { - // Store root signature in cross-transaction cache so subsequent - // transactions can attach it to requests and use it as color proof. if (rootSignature && rootSignature.length > 0) { - this._signatureCache.set(rootFromServer, rootSignature); + this._rootSignature = rootSignature; } // New path: server created/returned the root @@ -287,6 +279,9 @@ export class PlClient { return await altRoot.globalId; }, + rootSignature !== undefined + ? { signatureResolver: (id) => (id === rootFromServer ? rootSignature : undefined) } + : undefined, ); } } else { @@ -363,16 +358,13 @@ export class PlClient { const tx = new PlTransaction(llTx, name, writable, clientRoot, { finalPredicate: this.finalPredicate, resourceDataCache: this.resourceDataCache, - signatureCache: this._signatureCache, + signatureResolver: ops?.signatureResolver, }); // Auto-set default color proof so that resource creation (write TXs) // and name lookups (read TXs) carry the correct access color. - if (!isNullResourceId(clientRoot)) { - const rootSig = this._signatureCache.get(clientRoot); - if (rootSig) { - tx.setDefaultColor(rootSig); - } + if (!isNullResourceId(clientRoot) && this._rootSignature) { + tx.setDefaultColor(this._rootSignature); } let ok = false; @@ -394,14 +386,6 @@ export class PlClient { } else { // collecting stat this._txErrorStat = addStat(this._txErrorStat, tx.stat); - // Invalidate all cached signatures on permission denied. - // Targeted invalidation is impractical here because the failing - // resource id is not reliably available from the error. A full - // clear is safe: signatures are re-populated lazily from server - // responses in subsequent transactions. - if (isPermissionDenied(e)) { - this._signatureCache.clear(); - } throw e; } } finally { diff --git a/lib/node/pl-client/src/core/ll_client.test.ts b/lib/node/pl-client/src/core/ll_client.test.ts index 3a57756fb5..88ce986ad4 100644 --- a/lib/node/pl-client/src/core/ll_client.test.ts +++ b/lib/node/pl-client/src/core/ll_client.test.ts @@ -146,7 +146,6 @@ test("test https call via proxy", async () => { test("list user resources returns user root", async () => { const client = await getTestLLClient(); const responses = await client.listUserResources({ limit: 1 }); - expect(responses.length).toBe(1); - const msg = responses[0]; - expect(msg.entry.oneofKind).toBe("userRoot"); + const userRoots = responses.filter((r) => r.entry.oneofKind === "userRoot"); + expect(userRoots.length).toBe(1); }); diff --git a/lib/node/pl-client/src/core/signature_cache.ts b/lib/node/pl-client/src/core/signature_cache.ts deleted file mode 100644 index a0852e08a6..0000000000 --- a/lib/node/pl-client/src/core/signature_cache.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { LRUCache } from "lru-cache"; -import type { ResourceId, ResourceSignature, SignedResourceId } from "./types"; - -const DEFAULT_MAX_ENTRIES = 100_000; - -/** Cross-transaction cache for resource signatures. - * Keyed by ResourceId, stores opaque ResourceSignature bytes. - * Uses LRU eviction to prevent unbounded memory growth in long-running clients. */ -export class SignatureCache { - private readonly store: LRUCache; - - constructor(maxEntries: number = DEFAULT_MAX_ENTRIES) { - this.store = new LRUCache({ max: maxEntries }); - } - - set(id: ResourceId, sig: ResourceSignature): void { - this.store.set(id, sig); - } - - get(id: ResourceId): ResourceSignature | undefined { - return this.store.get(id); - } - - /** Return a SignedResourceId by looking up the cached signature. */ - sign(id: ResourceId): SignedResourceId { - return { resourceId: id, resourceSignature: this.get(id) }; - } - - delete(id: ResourceId): boolean { - return this.store.delete(id); - } - - clear(): void { - this.store.clear(); - } - - has(id: ResourceId): boolean { - return this.store.has(id); - } - - get size(): number { - return this.store.size; - } -} diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 2c2207417b..336842bce3 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -13,8 +13,6 @@ import type { ResourceSignature, ResourceType, FutureFieldType, - SignedResourceId, - SignedFieldId, } from "./types"; import { createLocalResourceId, @@ -45,7 +43,6 @@ import { isNotFoundError } from "./errors"; import type { FinalResourceDataPredicate } from "./final"; import type { LRUCache } from "lru-cache"; import type { ResourceDataCacheRecord } from "./cache"; -import type { SignatureCache } from "./signature_cache"; import type { TxStat } from "./stat"; import { initialTxStatWithoutTime } from "./stat"; import type { ErrorResourceData } from "./error_resource"; @@ -88,7 +85,7 @@ export type FieldRef = _FieldId; export type LocalFieldId = _FieldId; export type AnyFieldId = FieldId | LocalFieldId; -export type AnyResourceRef = ResourceRef | ResourceId; +export type AnyResourceRef = ResourceRef | AnyResourceId; export type AnyFieldRef = _FieldId; // FieldRef | FieldId export type AnyRef = AnyResourceRef | AnyFieldRef; @@ -97,9 +94,7 @@ export function isField(ref: AnyRef): ref is AnyFieldRef { } export function isResource(ref: AnyRef): ref is AnyResourceRef { - return ( - typeof ref === "bigint" || (ref.hasOwnProperty("globalId") && ref.hasOwnProperty("localId")) - ); + return typeof ref === "bigint" || isResourceRef(ref); } export function isResourceId(ref: AnyRef): ref is ResourceId { @@ -110,29 +105,30 @@ export function isFieldRef(ref: AnyFieldRef): ref is FieldRef { return isResourceRef(ref.resourceId); } -export function isResourceRef(ref: AnyResourceRef): ref is ResourceRef { - return ref.hasOwnProperty("globalId") && ref.hasOwnProperty("localId"); +export function isResourceRef(ref: AnyRef): ref is ResourceRef { + return typeof ref !== "bigint" && ref.hasOwnProperty("globalId") && ref.hasOwnProperty("localId"); } + export function toFieldId(ref: AnyFieldRef): AnyFieldId { if (isFieldRef(ref)) return { resourceId: ref.resourceId.localId, fieldName: ref.fieldName }; - else return ref as FieldId; + return ref as AnyFieldId; } export async function toGlobalFieldId(ref: AnyFieldRef): Promise { if (isFieldRef(ref)) return { resourceId: await ref.resourceId.globalId, fieldName: ref.fieldName }; - else return ref as FieldId; + return { resourceId: ref.resourceId as ResourceId, fieldName: ref.fieldName }; } export function toResourceId(ref: AnyResourceRef): AnyResourceId { if (isResourceRef(ref)) return ref.localId; - else return ref; + return ref; } export async function toGlobalResourceId(ref: AnyResourceRef): Promise { if (isResourceRef(ref)) return await ref.globalId; - else return ref; + return ref as ResourceId; } export function field(resourceId: AnyResourceRef, fieldName: string): AnyFieldRef { @@ -153,10 +149,12 @@ async function notFoundToUndefined(cb: () => Promise): Promise ResourceSignature | undefined; + export type PlTransactionOptions = { finalPredicate: FinalResourceDataPredicate; + signatureResolver?: SignatureResolver; resourceDataCache: LRUCache; - signatureCache?: SignatureCache; enableFormattedErrors?: boolean; }; @@ -203,7 +201,7 @@ export class PlTransaction { private readonly finalPredicate: FinalResourceDataPredicate; private readonly sharedResourceDataCache: LRUCache; - private readonly sharedSignatureCache?: SignatureCache; + private readonly signatureResolver?: SignatureResolver; private readonly enableFormattedErrors: boolean; constructor( @@ -215,7 +213,7 @@ export class PlTransaction { ) { this.finalPredicate = options.finalPredicate; this.sharedResourceDataCache = options.resourceDataCache; - this.sharedSignatureCache = options.signatureCache; + this.signatureResolver = options.signatureResolver; this.enableFormattedErrors = options.enableFormattedErrors ?? false; // initiating transaction @@ -307,35 +305,32 @@ export class PlTransaction { const rs = toResourceSignature(sig); if (rs && !this.signatureStore.has(id)) { this.signatureStore.set(id, rs); - if (!isLocalResourceId(id)) { - this.sharedSignatureCache?.set(id, rs); - } } } private getSignature(rId: AnyResourceId): ResourceSignature | undefined { return ( this.signatureStore.get(rId) ?? - (!isLocalResourceId(rId) ? this.sharedSignatureCache?.get(rId) : undefined) + (!isLocalResourceId(rId) ? this.signatureResolver?.(rId as ResourceId) : undefined) ); } - private toSignedResourceId(rId: AnyResourceRef): SignedResourceId { + private toSignedResourceId(rId: AnyResourceRef): { resourceId: AnyResourceId; resourceSignature?: ResourceSignature } { const resourceId = toResourceId(rId); return { resourceId, resourceSignature: this.getSignature(resourceId) }; } - private toSignedFieldId(fId: AnyFieldRef): SignedFieldId { + private toSignedFieldId(fId: AnyFieldRef): { resourceId: AnyResourceId; resourceSignature?: ResourceSignature; fieldName: string } { const base = toFieldId(fId); - return { ...base, resourceSignature: this.getSignature(base.resourceId) }; + return { resourceId: base.resourceId, fieldName: base.fieldName, resourceSignature: this.getSignature(base.resourceId) }; } private toSignedErrorRef(rId: AnyResourceRef): { errorResourceId: AnyResourceId; errorResourceSignature?: ResourceSignature; } { - const signed = this.toSignedResourceId(rId); - return { errorResourceId: signed.resourceId, errorResourceSignature: signed.resourceSignature }; + const { resourceId, resourceSignature } = this.toSignedResourceId(rId); + return { errorResourceId: resourceId, errorResourceSignature: resourceSignature }; } private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { @@ -831,7 +826,10 @@ export class PlTransaction { this._stat.fieldsCreated++; this.sendVoidAsync({ oneofKind: "fieldCreate", - fieldCreate: { type: fieldTypeToProto(fieldType), id: this.toSignedFieldId(fId) }, + fieldCreate: { + type: fieldTypeToProto(fieldType), + id: this.toSignedFieldId(fId), + }, }); if (value !== undefined) this.setField(fId, value); } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index 878b852bda..199aae06c2 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -4,8 +4,12 @@ import { cachedDeserialize, notEmpty } from "@milaboratories/ts-helpers"; declare const __resource_id_type__: unique symbol; type BrandResourceId = bigint & { [__resource_id_type__]: B }; -/** Global resource id */ -export type ResourceId = BrandResourceId<"global">; +/** Brand indicating this resource ID carries a server-issued authorization signature. */ +declare const __signed_resource__: unique symbol; +type BrandSignedResource = { readonly [__signed_resource__]: S }; + +/** Global resource id — always signed (received from server). */ +export type ResourceId = BrandResourceId<"global"> & BrandSignedResource; /** Null resource id */ export type NullResourceId = BrandResourceId<"null">; @@ -75,17 +79,6 @@ export type ResourceSignature = Uint8Array & { readonly [__resource_signature_ty /** Color proof used for resource creation requests (alias for ResourceSignature). */ export type ColorProof = ResourceSignature; -/** Resource ID bundled with its authorization signature. */ -export type SignedResourceId = { - readonly resourceId: AnyResourceId; - readonly resourceSignature?: ResourceSignature; -}; - -/** Signed field reference — resource signature attached to field's parent resource. */ -export type SignedFieldId = SignedResourceId & { - readonly fieldName: string; -}; - /** Encode resource signature to standard base64 for REST API bodies. */ export function signatureToBase64(sig?: ResourceSignature): string { return sig ? Buffer.from(sig).toString("base64") : ""; diff --git a/lib/node/pl-client/src/index.ts b/lib/node/pl-client/src/index.ts index a0aa1470d8..bfd16cc64a 100644 --- a/lib/node/pl-client/src/index.ts +++ b/lib/node/pl-client/src/index.ts @@ -5,7 +5,6 @@ export * from "./core/client"; export * from "./core/driver"; export * from "./core/transaction"; export * from "./core/errors"; -export * from "./core/signature_cache"; export * from "./core/default_client"; export * from "./core/unauth_client"; export * from "./core/auth"; diff --git a/lib/node/pl-drivers/src/drivers/ls.ts b/lib/node/pl-drivers/src/drivers/ls.ts index 31e4e38c8d..fac813d36c 100644 --- a/lib/node/pl-drivers/src/drivers/ls.ts +++ b/lib/node/pl-drivers/src/drivers/ls.ts @@ -1,4 +1,4 @@ -import type { PlClient, ResourceData, ResourceId, SignatureCache } from "@milaboratories/pl-client"; +import type { PlClient, ResourceData, ResourceId, ResourceSignature } from "@milaboratories/pl-client"; import { isNotNullResourceId } from "@milaboratories/pl-client"; import type * as sdk from "@milaboratories/pl-model-common"; import type { @@ -31,6 +31,8 @@ import { import type { LocalStorageProjection, VirtualLocalStorageSpec } from "./types"; import { DefaultVirtualLocalStorages } from "./virtual_storages"; +type StorageEntry = { id: ResourceId; signature?: ResourceSignature }; + /** * Extends public and safe SDK's driver API with methods used internally in the middle * layer and in tests. @@ -66,16 +68,14 @@ export class LsDriver implements InternalLsDriver { private constructor( private readonly logger: MiLogger, private readonly lsClient: ClientLs, - /** Pl storage id, to resource id. The resource id can be used to make LS GRPC calls to. */ - private readonly storageIdToResourceId: Record, + /** Pl storage id, to storage entry (resource id + signature). */ + private readonly storageIdToResourceId: Record, private readonly signer: Signer, /** Virtual storages by name */ private readonly virtualStoragesMap: Map, /** Local projections by storageId */ private readonly localProjectionsMap: Map, private readonly openFileDialogCallback: OpenFileDialogCallback, - /** Cross-transaction signature cache for resource authorization */ - private readonly signatureCache?: SignatureCache, ) {} public async getLocalFileContent( @@ -194,14 +194,12 @@ export class LsDriver implements InternalLsDriver { initialFullPath: s.initialPath, })); - const otherStorages = Object.entries(this.storageIdToResourceId!).map( - ([storageId, resourceId]) => ({ - name: storageId, - handle: createRemoteStorageHandle(storageId, resourceId), - initialFullPath: "", // we don't have any additional information from where to start browsing remote storages - isInitialPathHome: false, - }), - ); + const otherStorages = Object.entries(this.storageIdToResourceId!).map(([storageId, entry]) => ({ + name: storageId, + handle: createRemoteStorageHandle(storageId, entry.id), + initialFullPath: "", // we don't have any additional information from where to start browsing remote storages + isInitialPathHome: false, + })); // root must be a storage so we can index any file, // but for UI it's enough @@ -212,13 +210,11 @@ export class LsDriver implements InternalLsDriver { return [...virtualStorages, ...noRoot]; } - /** Enrich parsed storage data with resource signature from cache */ + /** Enrich parsed storage data with resource signature stored alongside its ID */ private withSignature(storageData: StorageHandleData): StorageHandleData { - if (storageData.isRemote && this.signatureCache) { - const sig = this.signatureCache.get(storageData.id); - if (sig) { - return { ...storageData, resourceSignature: sig }; - } + if (storageData.isRemote) { + const sig = this.storageIdToResourceId[storageData.name]?.signature; + if (sig) return { ...storageData, resourceSignature: sig }; } return storageData; } @@ -334,12 +330,11 @@ export class LsDriver implements InternalLsDriver { virtualStoragesMap, localProjectionsMap, openFileDialogCallback, - client.signatureCache, ); } } -async function doGetAvailableStorageIds(client: PlClient): Promise> { +async function doGetAvailableStorageIds(client: PlClient): Promise> { return client.withReadTx("GetAvailableStorageIds", async (tx) => { const lsProviderId = await tx.getResourceByName("LSProvider"); const provider = await tx.getResourceData(lsProviderId, true); @@ -348,10 +343,13 @@ async function doGetAvailableStorageIds(client: PlClient): Promise { return Object.fromEntries( provider.fields .filter((f) => f.type == "Dynamic" && isNotNullResourceId(f.value)) - .map((f) => [f.name.substring("storage/".length), f.value as ResourceId]), + .map((f) => [ + f.name.substring("storage/".length), + { id: f.value as ResourceId, signature: f.valueSignature }, + ]), ); } diff --git a/lib/node/pl-tree/src/state.ts b/lib/node/pl-tree/src/state.ts index ac5b7ad667..b9d80b5d13 100644 --- a/lib/node/pl-tree/src/state.ts +++ b/lib/node/pl-tree/src/state.ts @@ -100,13 +100,13 @@ export class PlTreeResource implements ResourceDataWithFinalState { kvChangedPerKey? = new KeyedChangeSource(); readonly id: ResourceId; + readonly resourceSignature?: ResourceSignature; originalResourceId: OptionalResourceId; readonly kind: ResourceKind; readonly type: ResourceType; readonly data?: Uint8Array; - resourceSignature?: ResourceSignature; private dataAsString?: string; private dataAsJson?: unknown; @@ -458,9 +458,6 @@ export class PlTreeState { // updating resource version, even if it was not changed resource.version += 1; - // always update signature to latest from server - resource.resourceSignature = rd.resourceSignature; - // duplicate / original if (resource.originalResourceId !== rd.originalResourceId) { if (resource.originalResourceId !== NullResourceId) @@ -794,6 +791,10 @@ export class PlTreeState { return this.entry(rid); } + public getResourceSignature(rid: ResourceId): ResourceSignature | undefined { + return this.resources.get(rid)?.resourceSignature; + } + public entry(rid: ResourceId = this.root): PlTreeEntry { this.checkValid(); return new PlTreeEntry({ treeProvider: () => this }, rid); diff --git a/lib/node/pl-tree/src/synchronized_tree.ts b/lib/node/pl-tree/src/synchronized_tree.ts index 55e205d124..89ae36b9b3 100644 --- a/lib/node/pl-tree/src/synchronized_tree.ts +++ b/lib/node/pl-tree/src/synchronized_tree.ts @@ -124,12 +124,14 @@ export class SynchronizedTreeState { private async refresh(stats?: TreeLoadingStat, txOps?: TxOps): Promise { if (this.terminated) throw new Error("tree synchronization is terminated"); const request = constructTreeLoadingRequest(this.state, this.pruning); + const treeState = this.state; const data = await this.pl.withReadTx( "ReadingTree", - async (tx) => { - return await loadTreeState(tx, request, stats); + async (tx) => loadTreeState(tx, request, stats), + { + ...txOps, + signatureResolver: (id) => treeState.getResourceSignature(id), }, - txOps, ); this.state.updateFromResourceData(data, true); } From 2660124cdd8af4040bc0bd5edc4317856541bb03 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 15:30:57 +0200 Subject: [PATCH 25/27] MILAB-5815: ref: resource id [drafr] --- lib/node/pl-client/src/core/client.ts | 4 +- lib/node/pl-client/src/core/transaction.ts | 109 +++++++++--------- .../pl-client/src/core/type_conversion.ts | 28 +++-- lib/node/pl-client/src/core/types.ts | 75 +++++++----- lib/node/pl-drivers/src/clients/download.ts | 4 +- lib/node/pl-drivers/src/clients/logs.ts | 8 +- lib/node/pl-drivers/src/clients/ls_api.ts | 4 +- lib/node/pl-drivers/src/clients/progress.ts | 4 +- lib/node/pl-drivers/src/clients/upload.ts | 16 +-- .../src/drivers/download_blob/blob_key.ts | 2 +- .../src/drivers/download_blob_url/driver.ts | 2 +- .../drivers/download_blob_url/driver_id.ts | 2 +- .../drivers/helpers/download_remote_handle.ts | 2 +- .../src/drivers/helpers/logs_handle.ts | 4 +- .../src/drivers/helpers/ls_storage_entry.ts | 2 +- .../src/middle_layer/middle_layer.ts | 30 ++--- .../src/middle_layer/project.ts | 2 +- .../src/middle_layer/project_list.ts | 2 +- .../src/model/project_model.ts | 2 +- lib/node/pl-tree/src/state.ts | 65 ++++++----- lib/node/pl-tree/src/test_utils.ts | 6 +- 21 files changed, 204 insertions(+), 169 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index 097ebd6d06..a4e8a2d197 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -4,7 +4,7 @@ import { LLPlClient } from "./ll_client"; import type { AnyResourceRef, SignatureResolver } from "./transaction"; import { PlTransaction, toGlobalResourceId, TxCommitConflict } from "./transaction"; import { createHash } from "node:crypto"; -import type { OptionalResourceId, ResourceId, ResourceSignature } from "./types"; +import type { GlobalResourceId, OptionalResourceId, ResourceId, ResourceSignature } from "./types"; import { bigintToResourceId, ensureResourceIdNotNull, @@ -96,7 +96,7 @@ export class PlClient { public readonly finalPredicate: FinalResourceDataPredicate; /** Resource data cache, to minimize redundant data rereading from remote db */ - private readonly resourceDataCache: LRUCache; + private readonly resourceDataCache: LRUCache; /** Root signature (user's actual root, used as default color proof) */ private _rootSignature?: ResourceSignature; diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index 336842bce3..e417fbeb10 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -3,6 +3,7 @@ import type { AnyResourceId, ColorProof, + GlobalResourceId, LocalResourceId, OptionalResourceId, BasicResourceData, @@ -94,11 +95,18 @@ export function isField(ref: AnyRef): ref is AnyFieldRef { } export function isResource(ref: AnyRef): ref is AnyResourceRef { - return typeof ref === "bigint" || isResourceRef(ref); + if (typeof ref === "bigint") return true; // LocalResourceId or NullResourceId + return isResourceRef(ref) || isResourceId(ref); } export function isResourceId(ref: AnyRef): ref is ResourceId { - return typeof ref === "bigint" && !isLocalResourceId(ref) && !isNullResourceId(ref); + return ( + typeof ref === "object" && + ref !== null && + "id" in ref && + !("globalId" in ref) && + !("resourceId" in ref) + ); } export function isFieldRef(ref: AnyFieldRef): ref is FieldRef { @@ -106,7 +114,12 @@ export function isFieldRef(ref: AnyFieldRef): ref is FieldRef { } export function isResourceRef(ref: AnyRef): ref is ResourceRef { - return typeof ref !== "bigint" && ref.hasOwnProperty("globalId") && ref.hasOwnProperty("localId"); + return ( + typeof ref === "object" && + ref !== null && + ref.hasOwnProperty("globalId") && + ref.hasOwnProperty("localId") + ); } @@ -154,7 +167,7 @@ export type SignatureResolver = (id: ResourceId) => ResourceSignature | undefine export type PlTransactionOptions = { finalPredicate: FinalResourceDataPredicate; signatureResolver?: SignatureResolver; - resourceDataCache: LRUCache; + resourceDataCache: LRUCache; enableFormattedErrors?: boolean; }; @@ -175,8 +188,8 @@ export class PlTransaction { private localResourceIdCounter = 0; - /** Maps resource ids (both local and global) to their signatures received from the server */ - private readonly signatureStore = new Map(); + /** Maps local resource ids to their signatures until the resource is globalized */ + private readonly signatureStore = new Map(); /** Default color proof to include in resource creation requests */ private defaultColorProof?: ColorProof; @@ -200,7 +213,7 @@ export class PlTransaction { } private readonly finalPredicate: FinalResourceDataPredicate; - private readonly sharedResourceDataCache: LRUCache; + private readonly sharedResourceDataCache: LRUCache; private readonly signatureResolver?: SignatureResolver; private readonly enableFormattedErrors: boolean; @@ -301,7 +314,7 @@ export class PlTransaction { void this.track(this.sendVoidSync(r)); } - private storeSignature(id: AnyResourceId, sig?: Uint8Array): void { + private storeSignature(id: LocalResourceId, sig?: Uint8Array): void { const rs = toResourceSignature(sig); if (rs && !this.signatureStore.has(id)) { this.signatureStore.set(id, rs); @@ -309,38 +322,37 @@ export class PlTransaction { } private getSignature(rId: AnyResourceId): ResourceSignature | undefined { - return ( - this.signatureStore.get(rId) ?? - (!isLocalResourceId(rId) ? this.signatureResolver?.(rId as ResourceId) : undefined) - ); + if (typeof rId !== "bigint") { + // ResourceId (object): prefer embedded signature, then resolver + return rId.signature ?? this.signatureStore.get(rId.id) ?? this.signatureResolver?.(rId); + } + // LocalResourceId: look in store + return this.signatureStore.get(rId); } - private toSignedResourceId(rId: AnyResourceRef): { resourceId: AnyResourceId; resourceSignature?: ResourceSignature } { + private toSignedResourceId(rId: AnyResourceRef): { resourceId: bigint; resourceSignature?: ResourceSignature } { const resourceId = toResourceId(rId); - return { resourceId, resourceSignature: this.getSignature(resourceId) }; + const rawId: bigint = typeof resourceId === "bigint" ? resourceId : resourceId.id; + return { resourceId: rawId, resourceSignature: this.getSignature(resourceId) }; } - private toSignedFieldId(fId: AnyFieldRef): { resourceId: AnyResourceId; resourceSignature?: ResourceSignature; fieldName: string } { + private toSignedFieldId(fId: AnyFieldRef): { resourceId: bigint; resourceSignature?: ResourceSignature; fieldName: string } { const base = toFieldId(fId); - return { resourceId: base.resourceId, fieldName: base.fieldName, resourceSignature: this.getSignature(base.resourceId) }; + const rawId: bigint = typeof base.resourceId === "bigint" ? base.resourceId : base.resourceId.id; + return { resourceId: rawId, fieldName: base.fieldName, resourceSignature: this.getSignature(base.resourceId) }; } private toSignedErrorRef(rId: AnyResourceRef): { - errorResourceId: AnyResourceId; + errorResourceId: bigint; errorResourceSignature?: ResourceSignature; } { const { resourceId, resourceSignature } = this.toSignedResourceId(rId); return { errorResourceId: resourceId, errorResourceSignature: resourceSignature }; } - private storeSignaturesFromResourceData(result: BasicResourceData | ResourceData): void { - this.storeSignature(result.id, result.resourceSignature); - if ("fields" in result && result.fields) { - for (const f of result.fields) { - if (!isNullResourceId(f.value)) this.storeSignature(f.value, f.valueSignature); - if (!isNullResourceId(f.error)) this.storeSignature(f.error, f.errorSignature); - } - } + private storeSignaturesFromResourceData(_result: BasicResourceData | ResourceData): void { + // Signatures are now embedded in ResourceId objects (via protoToResource/protoToField). + // Local ID signatures are stored directly in createResource. } /** Set default color proof for subsequent resource creation requests */ @@ -481,13 +493,12 @@ export class PlTransaction { const localId = this.nextLocalResourceId(root); const globalId = this.sendSingleAndParse(req(localId), (r) => { - const id = parser(r) as ResourceId; - if (sigExtractor) { - const sig = sigExtractor(r); - this.storeSignature(id, sig); + const rawId = parser(r); + const sig = sigExtractor ? toResourceSignature(sigExtractor(r)) : undefined; + if (sig) { this.storeSignature(localId, sig); } - return id; + return { id: rawId as GlobalResourceId, signature: sig }; }); void this.track(globalId); @@ -610,9 +621,10 @@ export class PlTransaction { return this.sendSingleAndParse( { oneofKind: "resourceNameGet", resourceNameGet: { name } }, (r) => { - const id = ensureResourceIdNotNull(r.resourceNameGet.resourceId as OptionalResourceId); - this.storeSignature(id, r.resourceNameGet.resourceSignature); - return id; + const rawId = r.resourceNameGet.resourceId; + if (rawId === 0n) throw new Error("null resource id from getResourceByName"); + const sig = toResourceSignature(r.resourceNameGet.resourceSignature); + return { id: rawId as GlobalResourceId, signature: sig }; }, ); } @@ -674,20 +686,18 @@ export class PlTransaction { ignoreCache: boolean = false, ): Promise { return this.track(async () => { - if (!ignoreCache && !isResourceRef(rId) && !isLocalResourceId(rId)) { + if (!ignoreCache && isResourceId(rId)) { // checking if we can return result from cache - const fromCache = this.sharedResourceDataCache.get(rId); + const fromCache = this.sharedResourceDataCache.get(rId.id); if (fromCache && fromCache.cacheTxOpenTimestamp < this.txOpenTimestamp) { if (!loadFields) { this._stat.rGetDataCacheHits++; this._stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0; - this.storeSignaturesFromResourceData(fromCache.basicData); return fromCache.basicData; } else if (fromCache.data) { this._stat.rGetDataCacheHits++; this._stat.rGetDataCacheBytes += fromCache.basicData.data?.length ?? 0; this._stat.rGetDataCacheFields += fromCache.data.fields.length; - this.storeSignaturesFromResourceData(fromCache.data); return fromCache.data; } } @@ -701,11 +711,7 @@ export class PlTransaction { loadFields: loadFields, }, }, - (r) => { - const rd = protoToResource(notEmpty(r.resourceGet.resource)); - this.storeSignaturesFromResourceData(rd); - return rd; - }, + (r) => protoToResource(notEmpty(r.resourceGet.resource)), ); this._stat.rGetDataNetRequests++; @@ -714,9 +720,9 @@ export class PlTransaction { // we will cache only final resource data states // caching result even if we were ignore the cache - if (!isResourceRef(rId) && !isLocalResourceId(rId) && this.finalPredicate(result)) { + if (isResourceId(rId) && this.finalPredicate(result)) { deepFreeze(result); - const fromCache = this.sharedResourceDataCache.get(rId); + const fromCache = this.sharedResourceDataCache.get(rId.id); if (fromCache) { if (loadFields && !fromCache.data) { fromCache.data = result; @@ -727,13 +733,13 @@ export class PlTransaction { const basicData = extractBasicResourceData(result); deepFreeze(basicData); if (loadFields) - this.sharedResourceDataCache.set(rId, { + this.sharedResourceDataCache.set(rId.id, { basicData, data: result, cacheTxOpenTimestamp: this.txOpenTimestamp, }); else - this.sharedResourceDataCache.set(rId, { + this.sharedResourceDataCache.set(rId.id, { basicData, data: undefined, cacheTxOpenTimestamp: this.txOpenTimestamp, @@ -769,8 +775,8 @@ export class PlTransaction { ); // cleaning cache record if resource was removed from the db - if (result === undefined && !isResourceRef(rId) && !isLocalResourceId(rId)) - this.sharedResourceDataCache.delete(rId); + if (result === undefined && isResourceId(rId)) + this.sharedResourceDataCache.delete(rId.id); return result; }); @@ -883,12 +889,7 @@ export class PlTransaction { this._stat.fieldsGet++; return this.sendSingleAndParse( { oneofKind: "fieldGet", fieldGet: { field: this.toSignedFieldId(fId) } }, - (r) => { - const fd = protoToField(notEmpty(r.fieldGet.field)); - if (!isNullResourceId(fd.value)) this.storeSignature(fd.value, fd.valueSignature); - if (!isNullResourceId(fd.error)) this.storeSignature(fd.error, fd.errorSignature); - return fd; - }, + (r) => protoToField(notEmpty(r.fieldGet.field)), ); } diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index 8508f3a783..ee3de3ce08 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -15,6 +15,7 @@ import type { ResourceData, ResourceId, ResourceKind, + GlobalResourceId, } from "./types"; import { NullResourceId, toResourceSignature } from "./types"; import { assertNever, notEmpty } from "@milaboratories/ts-helpers"; @@ -26,12 +27,18 @@ function resourceIsDeleted(proto: Resource): boolean { return proto.deletedTime !== undefined && proto.deletedTime.seconds !== 0n; } +function protoIdToOptionalResourceId(id: bigint, signature?: Uint8Array): OptionalResourceId { + if (id === 0n) return NullResourceId; + return { id: id as GlobalResourceId, signature: toResourceSignature(signature) }; +} + /** Throws "native" pl not found error, if resource is marked as deleted. */ export function protoToResource(proto: Resource): ResourceData { if (resourceIsDeleted(proto)) throwPlNotFoundError("resource deleted"); + const resourceSignature = toResourceSignature(proto.resourceSignature); return { - id: proto.resourceId as ResourceId, - originalResourceId: proto.originalResourceId as OptionalResourceId, + id: { id: proto.resourceId as GlobalResourceId, signature: resourceSignature }, + originalResourceId: protoIdToOptionalResourceId(proto.originalResourceId), type: notEmpty(proto.type), data: proto.data, inputsLocked: proto.inputsLocked, @@ -40,7 +47,7 @@ export function protoToResource(proto: Resource): ResourceData { kind: protoToResourceKind(proto.kind), error: protoToError(proto), final: proto.isFinal, - resourceSignature: toResourceSignature(proto.resourceSignature), + resourceSignature, fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField), }; } @@ -58,19 +65,24 @@ function protoToResourceKind(proto: Resource_Kind): ResourceKind { function protoToError(proto: Resource): OptionalResourceId { const f = proto.fields.find((f) => f?.id?.fieldName === ResourceErrorField); - return (f?.error ?? NullResourceId) as OptionalResourceId; + if (!f) return NullResourceId; + const errId = f.error ?? 0n; + if (errId === 0n) return NullResourceId; + return { id: errId as GlobalResourceId, signature: toResourceSignature(f.errorSignature) }; } export function protoToField(proto: Field): FieldData { + const valueSignature = toResourceSignature(proto.valueSignature); + const errorSignature = toResourceSignature(proto.errorSignature); return { name: notEmpty(proto.id?.fieldName), type: protoToFieldType(proto.type), status: protoToFieldStatus(proto.valueStatus), - value: proto.value as OptionalResourceId, - error: proto.error as OptionalResourceId, + value: protoIdToOptionalResourceId(proto.value, proto.valueSignature), + error: protoIdToOptionalResourceId(proto.error, proto.errorSignature), valueIsFinal: proto.valueIsFinal, - valueSignature: toResourceSignature(proto.valueSignature), - errorSignature: toResourceSignature(proto.errorSignature), + valueSignature, + errorSignature, }; } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index 199aae06c2..c4f0112a4b 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -4,19 +4,26 @@ import { cachedDeserialize, notEmpty } from "@milaboratories/ts-helpers"; declare const __resource_id_type__: unique symbol; type BrandResourceId = bigint & { [__resource_id_type__]: B }; -/** Brand indicating this resource ID carries a server-issued authorization signature. */ -declare const __signed_resource__: unique symbol; -type BrandSignedResource = { readonly [__signed_resource__]: S }; +/** Opaque authorization signature attached to a resource. */ +declare const __resource_signature_type__: unique symbol; +export type ResourceSignature = Uint8Array & { readonly [__resource_signature_type__]: true }; -/** Global resource id — always signed (received from server). */ -export type ResourceId = BrandResourceId<"global"> & BrandSignedResource; +export type ResourceId = SignedResourceId /** Null resource id */ export type NullResourceId = BrandResourceId<"null">; +/** Global resource id — always signed (received from server). */ +export type GlobalResourceId = BrandResourceId<"global"> + /** Local resource id */ export type LocalResourceId = BrandResourceId<"local">; +export type SignedResourceId = { + id: GlobalResourceId, + signature?: ResourceSignature +} + /** Any non-null resource id */ export type AnyResourceId = ResourceId | LocalResourceId; @@ -28,12 +35,12 @@ export type OptionalAnyResourceId = NullResourceId | ResourceId | LocalResourceI export const NullResourceId = 0n as NullResourceId; -export function isNullResourceId(resourceId: bigint): resourceId is NullResourceId { - return resourceId === NullResourceId; +export function isNullResourceId(resourceId: OptionalAnyResourceId): resourceId is NullResourceId { + return typeof resourceId === "bigint" && resourceId === NullResourceId; } export function isNotNullResourceId(resourceId: OptionalResourceId): resourceId is ResourceId { - return resourceId !== NullResourceId; + return typeof resourceId !== "bigint"; } export function ensureResourceIdNotNull(resourceId: OptionalResourceId): ResourceId { @@ -41,8 +48,9 @@ export function ensureResourceIdNotNull(resourceId: OptionalResourceId): Resourc return resourceId; } -export function isAnyResourceId(resourceId: bigint): resourceId is AnyResourceId { - return resourceId !== 0n; +export function isAnyResourceId(resourceId: OptionalAnyResourceId): resourceId is AnyResourceId { + if (typeof resourceId === "bigint") return resourceId !== 0n; + return true; } // see local / global resource logic below... @@ -72,10 +80,6 @@ export function resourceTypesEqual(type1: ResourceType, type2: ResourceType): bo return type1.name === type2.name && type1.version === type2.version; } -/** Opaque authorization signature attached to a resource. */ -declare const __resource_signature_type__: unique symbol; -export type ResourceSignature = Uint8Array & { readonly [__resource_signature_type__]: true }; - /** Color proof used for resource creation requests (alias for ResourceSignature). */ export type ColorProof = ResourceSignature; @@ -227,8 +231,9 @@ export function createLocalResourceId( (BigInt(localTxId) << LocalResourceIdTxIdOffset)) as LocalResourceId; } -export function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint): ResourceId { - return ((isRoot ? ResourceIdRootMask : 0n) | unmaskedId) as ResourceId; +export function createGlobalResourceId(isRoot: boolean, unmaskedId: bigint, signature?: ResourceSignature): ResourceId { + const id = ((isRoot ? ResourceIdRootMask : 0n) | unmaskedId) as GlobalResourceId; + return { id, signature }; } export function extractTxId(localResourceId: LocalResourceId): number { @@ -236,6 +241,7 @@ export function extractTxId(localResourceId: LocalResourceId): number { } export function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxId: number): void { + if (typeof resourceId !== "bigint") return; // ResourceId (object) is never local if (!isLocalResourceId(resourceId)) return; if (extractTxId(resourceId) !== expectedTxId) throw Error( @@ -244,21 +250,22 @@ export function checkLocalityOfResourceId(resourceId: AnyResourceId, expectedTxI } export function resourceIdToString(resourceId: OptionalAnyResourceId): string { - if (isNullResourceId(resourceId)) return "XX:0x0"; - if (isLocalResourceId(resourceId)) + const raw: bigint = typeof resourceId === "bigint" ? resourceId : resourceId.id; + if (raw === 0n) return "XX:0x0"; + if ((raw & ResourceIdLocalMask) !== 0n) return ( - (isRootResourceId(resourceId) ? "R" : "N") + + (isRootResourceId(raw) ? "R" : "N") + "L:0x" + - (LocalIdMask & resourceId).toString(16) + + (LocalIdMask & raw).toString(16) + "[0x" + - extractTxId(resourceId).toString(16) + + extractTxId(raw as LocalResourceId).toString(16) + "]" ); else return ( - (isRootResourceId(resourceId) ? "R" : "N") + + (isRootResourceId(raw) ? "R" : "N") + "G:0x" + - (NoFlagsIdMask & resourceId).toString(16) + (NoFlagsIdMask & raw).toString(16) ); } @@ -276,15 +283,23 @@ export function resourceIdFromString(str: string): OptionalAnyResourceId | undef } /** Converts bigint to global resource id */ -export function bigintToResourceId(resourceId: bigint): ResourceId { +export function bigintToResourceId(resourceId: bigint, signature?: ResourceSignature): ResourceId { if (isLocalResourceId(resourceId)) - throw new Error(`Local resource id: ${resourceIdToString(resourceId)}`); - if (isNullResourceId(resourceId)) throw new Error(`Null resource id.`); - return resourceId as ResourceId; + throw new Error(`Local resource id: ${resourceIdToString(resourceId as LocalResourceId)}`); + if (isNullResourceId(resourceId as NullResourceId)) throw new Error(`Null resource id.`); + return { id: resourceId as GlobalResourceId, signature }; } export function stringifyWithResourceId(object: unknown): string { - return JSON.stringify(object, (key, value) => - typeof value === "bigint" ? resourceIdToString(value as OptionalAnyResourceId) : value, - ); + return JSON.stringify(object, (key, value) => { + if (typeof value === "bigint") return resourceIdToString(value as LocalResourceId); + if ( + typeof value === "object" && + value !== null && + "id" in value && + typeof (value as { id: unknown }).id === "bigint" + ) + return resourceIdToString(value as ResourceId); + return value; + }); } diff --git a/lib/node/pl-drivers/src/clients/download.ts b/lib/node/pl-drivers/src/clients/download.ts index 79107d30a4..abf83f7772 100644 --- a/lib/node/pl-drivers/src/clients/download.ts +++ b/lib/node/pl-drivers/src/clients/download.ts @@ -140,14 +140,14 @@ export class ClientDownload { const client = this.wire.get(); if (client instanceof DownloadClient) { return await client.getDownloadURL( - { resourceId: id, resourceSignature, isInternalUse: false }, + { resourceId: id.id, resourceSignature, isInternalUse: false }, addRTypeToMetadata(type, withAbort), ).response; } else { return ( await client.POST("/v1/get-download-url", { body: { - resourceId: id.toString(), + resourceId: id.id.toString(), resourceSignature: signatureToBase64(resourceSignature), isInternalUse: false, }, diff --git a/lib/node/pl-drivers/src/clients/logs.ts b/lib/node/pl-drivers/src/clients/logs.ts index a907ac5718..c69ef8bc3b 100644 --- a/lib/node/pl-drivers/src/clients/logs.ts +++ b/lib/node/pl-drivers/src/clients/logs.ts @@ -53,7 +53,7 @@ export class ClientLogs { return ( await client.lastLines( { - resourceId: rId, + resourceId: rId.id, resourceSignature: rSig, lineCount: lineCount, offset: offsetBytes, @@ -67,7 +67,7 @@ export class ClientLogs { const resp = ( await client.POST("/v1/last-lines", { body: { - resourceId: rId.toString(), + resourceId: rId.id.toString(), resourceSignature: signatureToBase64(rSig), lineCount: lineCount, offset: offsetBytes.toString(), @@ -101,7 +101,7 @@ export class ClientLogs { return ( await client.readText( { - resourceId: notEmpty(rId), + resourceId: rId.id, resourceSignature: rSig, readLimit: BigInt(lineCount), offset: offsetBytes, @@ -115,7 +115,7 @@ export class ClientLogs { const resp = ( await client.POST("/v1/read/text", { body: { - resourceId: rId.toString(), + resourceId: rId.id.toString(), resourceSignature: signatureToBase64(rSig), readLimit: lineCount.toString(), offset: offsetBytes.toString(), diff --git a/lib/node/pl-drivers/src/clients/ls_api.ts b/lib/node/pl-drivers/src/clients/ls_api.ts index cf0c936512..6db4dcf117 100644 --- a/lib/node/pl-drivers/src/clients/ls_api.ts +++ b/lib/node/pl-drivers/src/clients/ls_api.ts @@ -49,7 +49,7 @@ export class ClientLs { if (client instanceof LSClient) { return await client.list( { - resourceId: rInfo.id, + resourceId: rInfo.id.id, resourceSignature: rInfo.resourceSignature, location: path, }, @@ -59,7 +59,7 @@ export class ClientLs { const resp = ( await client.POST("/v1/list", { body: { - resourceId: rInfo.id.toString(), + resourceId: rInfo.id.id.toString(), resourceSignature: signatureToBase64(rInfo.resourceSignature), location: path, }, diff --git a/lib/node/pl-drivers/src/clients/progress.ts b/lib/node/pl-drivers/src/clients/progress.ts index 3cd9050747..cb9f482216 100644 --- a/lib/node/pl-drivers/src/clients/progress.ts +++ b/lib/node/pl-drivers/src/clients/progress.ts @@ -66,7 +66,7 @@ export class ClientProgress { report = notEmpty( ( await client.getStatus( - { resourceId: id, resourceSignature }, + { resourceId: id.id, resourceSignature }, addRTypeToMetadata(type, options), ).response ).report, @@ -75,7 +75,7 @@ export class ClientProgress { const resp = ( await client.POST("/v1/get-progress", { body: { - resourceId: id.toString(), + resourceId: id.id.toString(), resourceSignature: signatureToBase64(resourceSignature), }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/clients/upload.ts b/lib/node/pl-drivers/src/clients/upload.ts index 2ec0fd666e..6efb937845 100644 --- a/lib/node/pl-drivers/src/clients/upload.ts +++ b/lib/node/pl-drivers/src/clients/upload.ts @@ -87,7 +87,7 @@ export class ClientUpload { if (client instanceof UploadClient) { const init = ( - await client.init({ resourceId: id, resourceSignature }, addRTypeToMetadata(type, options)) + await client.init({ resourceId: id.id, resourceSignature }, addRTypeToMetadata(type, options)) ).response; return { @@ -101,7 +101,7 @@ export class ClientUpload { const init = ( await client.POST("/v1/upload/init", { body: { - resourceId: id.toString(), + resourceId: id.id.toString(), resourceSignature: signatureToBase64(resourceSignature), }, headers: { ...createRTypeRoutingHeader(type) }, @@ -133,7 +133,7 @@ export class ClientUpload { info = ( await client.getPartURL( { - resourceId: id, + resourceId: id.id, resourceSignature, partNumber, uploadedPartSize: 0n, @@ -147,7 +147,7 @@ export class ClientUpload { const resp = ( await client.POST("/v1/upload/get-part-url", { body: { - resourceId: id.toString(), + resourceId: id.id.toString(), resourceSignature: signatureToBase64(resourceSignature), partNumber: partNumber.toString(), uploadedPartSize: "0", @@ -232,7 +232,7 @@ export class ClientUpload { if (client instanceof UploadClient) { await client.finalize( { - resourceId: info.id, + resourceId: info.id.id, resourceSignature: info.resourceSignature, checksumAlgorithm: UploadAPI_ChecksumAlgorithm.UNSPECIFIED, checksum: new Uint8Array(0), @@ -242,7 +242,7 @@ export class ClientUpload { } else { await client.POST("/v1/upload/finalize", { body: { - resourceId: info.id.toString(), + resourceId: info.id.id.toString(), resourceSignature: signatureToBase64(info.resourceSignature), checksumAlgorithm: 0, checksum: "", @@ -275,7 +275,7 @@ export class ClientUpload { if (client instanceof UploadClient) { await client.updateProgress( { - resourceId: id, + resourceId: id.id, resourceSignature, bytesProcessed, }, @@ -286,7 +286,7 @@ export class ClientUpload { await client.POST("/v1/upload/update-progress", { body: { - resourceId: id.toString(), + resourceId: id.id.toString(), resourceSignature: signatureToBase64(resourceSignature), bytesProcessed: bytesProcessed.toString(), }, diff --git a/lib/node/pl-drivers/src/drivers/download_blob/blob_key.ts b/lib/node/pl-drivers/src/drivers/download_blob/blob_key.ts index f026181544..885226fad2 100644 --- a/lib/node/pl-drivers/src/drivers/download_blob/blob_key.ts +++ b/lib/node/pl-drivers/src/drivers/download_blob/blob_key.ts @@ -2,7 +2,7 @@ import { bigintToResourceId, ResourceId } from "@milaboratories/pl-client"; import * as path from "node:path"; export function blobKey(rId: ResourceId): string { - return `${BigInt(rId)}`; + return `${rId.id}`; } export function pathToKey(fPath: string): string { diff --git a/lib/node/pl-drivers/src/drivers/download_blob_url/driver.ts b/lib/node/pl-drivers/src/drivers/download_blob_url/driver.ts index f4ba1985ae..cd3505807b 100644 --- a/lib/node/pl-drivers/src/drivers/download_blob_url/driver.ts +++ b/lib/node/pl-drivers/src/drivers/download_blob_url/driver.ts @@ -225,6 +225,6 @@ export class DownloadBlobToURLDriver implements BlobToURLDriver { } private getFilePath(id: ResourceId, format: ArchiveFormat): string { - return path.join(this.saveDir, `${String(BigInt(id))}_${format}`); + return path.join(this.saveDir, `${String(id.id)}_${format}`); } } diff --git a/lib/node/pl-drivers/src/drivers/download_blob_url/driver_id.ts b/lib/node/pl-drivers/src/drivers/download_blob_url/driver_id.ts index 6899ce7922..7616b9a0fc 100644 --- a/lib/node/pl-drivers/src/drivers/download_blob_url/driver_id.ts +++ b/lib/node/pl-drivers/src/drivers/download_blob_url/driver_id.ts @@ -5,7 +5,7 @@ import type { ArchiveFormat } from "@milaboratories/pl-model-common"; export type Id = string; export function newId(id: ResourceId, format: ArchiveFormat): Id { - return `id:${String(BigInt(id))}-${format}`; + return `id:${String(id.id)}-${format}`; } // export function diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 462d12ff9f..678e56c777 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -19,7 +19,7 @@ export function newRemoteHandle( rInfo: OnDemandBlobResourceSnapshot, signer: Signer, ): RemoteBlobHandle { - let content = `${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}/${getSize(rInfo)}`; + let content = `${rInfo.type.name}/${rInfo.type.version}/${rInfo.id.id}/${getSize(rInfo)}`; const sigStr = signatureToBase64Url(rInfo.resourceSignature); if (sigStr) { content += `/${sigStr}`; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index 5dde5e9a5d..b485ffc1fe 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -13,10 +13,10 @@ export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHand const sigStr = signatureToBase64Url(rInfo.resourceSignature); const resSig = sigStr ? `/${sigStr}` : ""; if (live) { - return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.LiveLogHandle; + return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${rInfo.id.id}${resSig}` as sdk.LiveLogHandle; } - return `log+ready://log/${rInfo.type.name}/${rInfo.type.version}/${BigInt(rInfo.id)}${resSig}` as sdk.ReadyLogHandle; + return `log+ready://log/${rInfo.type.name}/${rInfo.type.version}/${rInfo.id.id}${resSig}` as sdk.ReadyLogHandle; } /** Handle of the live logs of a program. diff --git a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts index 0a52f2a803..5a8379ede7 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts @@ -72,7 +72,7 @@ export function isRemoteStorageHandle( } export function createRemoteStorageHandle(name: string, rId: ResourceId): sdk.StorageHandleRemote { - return `remote://${name}/${BigInt(rId)}`; + return `remote://${name}/${rId.id}`; } function parseRemoteStorageHandle(handle: string): RemoteStorageHandleData { diff --git a/lib/node/pl-middle-layer/src/middle_layer/middle_layer.ts b/lib/node/pl-middle-layer/src/middle_layer/middle_layer.ts index d86a4f2795..6d9eef29c2 100644 --- a/lib/node/pl-middle-layer/src/middle_layer/middle_layer.ts +++ b/lib/node/pl-middle-layer/src/middle_layer/middle_layer.ts @@ -1,4 +1,4 @@ -import type { PlClient, ResourceId } from "@milaboratories/pl-client"; +import type { GlobalResourceId, PlClient, ResourceId } from "@milaboratories/pl-client"; import { field, isNotNullResourceId, @@ -216,7 +216,7 @@ export class MiddleLayer { // Projects // - private readonly openedProjectsByRid = new Map(); + private readonly openedProjectsByRid = new Map(); private async projectIdToResourceId(id: string): Promise { return await this.pl.withReadTx("Project id to resource id", async (tx) => { @@ -234,31 +234,31 @@ export class MiddleLayer { /** Opens a project, and starts corresponding project maintenance loop. */ public async openProject(id: ResourceId | string) { const rid = await this.ensureProjectRid(id); - if (this.openedProjectsByRid.has(rid)) throw new Error(`Project ${rid} already opened`); - this.openedProjectsByRid.set(rid, await Project.init(this.env, rid)); - this.openedProjectsList.setValue([...this.openedProjectsByRid.keys()]); + if (this.openedProjectsByRid.has(rid.id)) throw new Error(`Project ${rid} already opened`); + this.openedProjectsByRid.set(rid.id, { rid, project: await Project.init(this.env, rid) }); + this.openedProjectsList.setValue([...this.openedProjectsByRid.values()].map((v) => v.rid)); } /** Closes the project, and deallocate all corresponding resources. */ public async closeProject(rid: ResourceId): Promise { - const prj = this.openedProjectsByRid.get(rid); - if (prj === undefined) throw new Error(`Project ${rid} not found among opened projects`); - this.openedProjectsByRid.delete(rid); - await prj.destroy(); - this.openedProjectsList.setValue([...this.openedProjectsByRid.keys()]); + const entry = this.openedProjectsByRid.get(rid.id); + if (entry === undefined) throw new Error(`Project ${rid} not found among opened projects`); + this.openedProjectsByRid.delete(rid.id); + await entry.project.destroy(); + this.openedProjectsList.setValue([...this.openedProjectsByRid.values()].map((v) => v.rid)); } /** Returns a project access object for opened project, for the given project * resource id. */ public getOpenedProject(rid: ResourceId): Project { - const prj = this.openedProjectsByRid.get(rid); - if (prj === undefined) throw new Error(`Project ${rid} not found among opened projects`); - return prj; + const entry = this.openedProjectsByRid.get(rid.id); + if (entry === undefined) throw new Error(`Project ${rid} not found among opened projects`); + return entry.project; } /** Returns true if project with given resource id is currently opened. */ public isProjectOpened(rid: ResourceId): boolean { - return this.openedProjectsByRid.has(rid); + return this.openedProjectsByRid.has(rid.id); } /** @@ -267,7 +267,7 @@ export class MiddleLayer { * them. */ public async close() { - await Promise.all([...this.openedProjectsByRid.values()].map((prj) => prj.destroy())); + await Promise.all([...this.openedProjectsByRid.values()].map((entry) => entry.project.destroy())); // this.env.quickJs; await this.projectListTree.terminate(); await this.env.dispose(); diff --git a/lib/node/pl-middle-layer/src/middle_layer/project.ts b/lib/node/pl-middle-layer/src/middle_layer/project.ts index f34554ae4b..f2ec2b3e35 100644 --- a/lib/node/pl-middle-layer/src/middle_layer/project.ts +++ b/lib/node/pl-middle-layer/src/middle_layer/project.ts @@ -120,7 +120,7 @@ export class Project { } get projectLockId(): string { - return "project:" + this.rid.toString(); + return "project:" + this.rid.id.toString(); } private async refreshLoop(): Promise { diff --git a/lib/node/pl-middle-layer/src/middle_layer/project_list.ts b/lib/node/pl-middle-layer/src/middle_layer/project_list.ts index 8e97c4766c..00737b6a74 100644 --- a/lib/node/pl-middle-layer/src/middle_layer/project_list.ts +++ b/lib/node/pl-middle-layer/src/middle_layer/project_list.ts @@ -55,7 +55,7 @@ export async function createProjectList( rid: prj.id, created: new Date(created), lastModified: new Date(lastModified), - opened: oProjects.indexOf(prj.id) >= 0, + opened: oProjects.some((id) => id.id === prj.id.id), meta, }); } diff --git a/lib/node/pl-middle-layer/src/model/project_model.ts b/lib/node/pl-middle-layer/src/model/project_model.ts index 57c1e6714b..4d28e22cfd 100644 --- a/lib/node/pl-middle-layer/src/model/project_model.ts +++ b/lib/node/pl-middle-layer/src/model/project_model.ts @@ -5,7 +5,7 @@ import type { } from "@milaboratories/pl-model-middle-layer"; import type { BlockRenderingMode } from "@platforma-sdk/model"; -export interface ProjectListEntry extends ProjectListEntryFromModel { +export interface ProjectListEntry extends Omit { /** Project resource ID. */ rid: ResourceId; } diff --git a/lib/node/pl-tree/src/state.ts b/lib/node/pl-tree/src/state.ts index b9d80b5d13..50d4f83a18 100644 --- a/lib/node/pl-tree/src/state.ts +++ b/lib/node/pl-tree/src/state.ts @@ -3,6 +3,7 @@ import type { FieldData, FieldStatus, FieldType, + GlobalResourceId, KeyValue, OptionalResourceId, ResourceData, @@ -18,6 +19,12 @@ import { resourceIdToString, stringifyWithResourceId, } from "@milaboratories/pl-client"; + +function sameOptionalResourceId(a: OptionalResourceId, b: OptionalResourceId): boolean { + if (isNullResourceId(a)) return isNullResourceId(b); + if (isNullResourceId(b)) return false; + return a.id === b.id; +} import type { Watcher } from "@milaboratories/computable"; import { ChangeSource, KeyedChangeSource } from "@milaboratories/computable"; import { PlTreeEntry } from "./accessors"; @@ -391,7 +398,7 @@ export class PlTreeResource implements ResourceDataWithFinalState { export class PlTreeState { /** resource heap */ - private resources: Map = new Map(); + private resources: Map = new Map(); private readonly resourcesAdded = new ChangeSource(); /** Resets to false if any invalid state transitions are registered, * after that tree will produce errors for any read or write operations */ @@ -414,7 +421,7 @@ export class PlTreeState { public get(watcher: Watcher, rid: ResourceId): PlTreeResource { this.checkValid(); - const res = this.resources.get(rid); + const res = this.resources.get(rid.id); if (res === undefined) { // to make recovery from resource not found possible, considering some // race conditions, where computable is created before tree is updated @@ -429,12 +436,12 @@ export class PlTreeState { this.checkValid(); // All resources for which recount should be incremented, first are aggregated in this list - const incrementRefs: ResourceId[] = []; - const decrementRefs: ResourceId[] = []; + const incrementRefs: GlobalResourceId[] = []; + const decrementRefs: GlobalResourceId[] = []; // patching / creating resources for (const rd of resourceData) { - let resource = this.resources.get(rd.id); + let resource = this.resources.get(rd.id.id); const statBeforeMutation = resource?.basicState; const unexpectedTransitionError = (reason: string): never => { @@ -459,7 +466,7 @@ export class PlTreeState { resource.version += 1; // duplicate / original - if (resource.originalResourceId !== rd.originalResourceId) { + if (!sameOptionalResourceId(resource.originalResourceId, rd.originalResourceId)) { if (resource.originalResourceId !== NullResourceId) unexpectedTransitionError("originalResourceId can't change after it is set"); resource.originalResourceId = rd.originalResourceId; @@ -471,11 +478,11 @@ export class PlTreeState { } // error - if (resource.error !== rd.error) { + if (!sameOptionalResourceId(resource.error, rd.error)) { if (isNotNullResourceId(resource.error)) unexpectedTransitionError("resource can't change attached error after it is set"); resource.error = rd.error; - incrementRefs.push(resource.error as ResourceId); + if (isNotNullResourceId(resource.error)) incrementRefs.push(resource.error.id); notEmpty(resource.resourceStateChange).markChanged( `error changed for ${resourceIdToString(resource.id)}`, ); @@ -498,8 +505,8 @@ export class PlTreeState { fd.valueIsFinal, resource.version, ); - if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value); - if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error); + if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value.id); + if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error.id); if (fd.type === "Input" || fd.type === "Service") { if (resource.inputsLocked) @@ -562,10 +569,10 @@ export class PlTreeState { } // field value - if (field.value !== fd.value) { - if (isNotNullResourceId(field.value)) decrementRefs.push(field.value); + if (!sameOptionalResourceId(field.value, fd.value)) { + if (isNotNullResourceId(field.value)) decrementRefs.push(field.value.id); field.value = fd.value; - if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value); + if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value.id); field.change.markChanged( `field ${fd.name} value changed in ${resourceIdToString(resource.id)}`, ); @@ -573,10 +580,10 @@ export class PlTreeState { } // field error - if (field.error !== fd.error) { - if (isNotNullResourceId(field.error)) decrementRefs.push(field.error); + if (!sameOptionalResourceId(field.error, fd.error)) { + if (isNotNullResourceId(field.error)) decrementRefs.push(field.error.id); field.error = fd.error; - if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error); + if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error.id); field.change.markChanged( `field ${fd.name} error changed in ${resourceIdToString(resource.id)}`, ); @@ -615,8 +622,8 @@ export class PlTreeState { ); fields.delete(fieldName); - if (isNotNullResourceId(field.value)) decrementRefs.push(field.value); - if (isNotNullResourceId(field.error)) decrementRefs.push(field.error); + if (isNotNullResourceId(field.value)) decrementRefs.push(field.value.id); + if (isNotNullResourceId(field.error)) decrementRefs.push(field.error.id); notEmpty(resource!.dynamicFieldListChanged).markChanged( `dynamic field ${fieldName} removed from ${resourceIdToString(resource!.id)}`, @@ -704,7 +711,7 @@ export class PlTreeState { resource = new PlTreeResource(rd); resource.verifyReadyState(); - if (isNotNullResourceId(resource.error)) incrementRefs.push(resource.error); + if (isNotNullResourceId(resource.error)) incrementRefs.push(resource.error.id); for (const fd of rd.fields) { const field = new PlTreeField( fd.name, @@ -715,8 +722,8 @@ export class PlTreeState { fd.valueIsFinal, InitialResourceVersion, ); - if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value); - if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error); + if (isNotNullResourceId(fd.value)) incrementRefs.push(fd.value.id); + if (isNotNullResourceId(fd.error)) incrementRefs.push(fd.error.id); resource.fieldsMap.set(fd.name, field); } @@ -727,7 +734,7 @@ export class PlTreeState { if (this.isFinalPredicate(resource)) resource.markFinal(); // adding the resource to the heap - this.resources.set(resource.id, resource); + this.resources.set(resource.id.id, resource); this.resourcesAdded.markChanged(`new resource ${resourceIdToString(resource.id)} added`); } } @@ -745,7 +752,7 @@ export class PlTreeState { // recursively applying refCount decrements / doing garbage collection let currentRefs = decrementRefs; while (currentRefs.length > 0) { - const nextRefs: ResourceId[] = []; + const nextRefs: GlobalResourceId[] = []; for (const rid of currentRefs) { const res = this.resources.get(rid); if (!res) { @@ -755,16 +762,16 @@ export class PlTreeState { res.refCount--; // garbage collection - if (res.refCount === 0 && res.id !== this.root) { + if (res.refCount === 0 && res.id.id !== this.root.id) { // removing fields res.fieldsMap.forEach((field) => { - if (isNotNullResourceId(field.value)) nextRefs.push(field.value); - if (isNotNullResourceId(field.error)) nextRefs.push(field.error); + if (isNotNullResourceId(field.value)) nextRefs.push(field.value.id); + if (isNotNullResourceId(field.error)) nextRefs.push(field.error.id); field.change.markChanged( `field ${field.name} removed during garbage collection of ${resourceIdToString(res.id)}`, ); }); - if (isNotNullResourceId(res.error)) nextRefs.push(res.error); + if (isNotNullResourceId(res.error)) nextRefs.push(res.error.id); res.resourceRemoved.markChanged( `resource removed during garbage collection: ${resourceIdToString(res.id)}`, ); @@ -777,7 +784,7 @@ export class PlTreeState { // checking for orphans (maybe removed in the future) if (!allowOrphanInputs) { for (const rd of resourceData) { - if (!this.resources.has(rd.id)) { + if (!this.resources.has(rd.id.id)) { this.invalidateTree(); throw new TreeStateUpdateError(`orphan input resource ${rd.id}`); } @@ -792,7 +799,7 @@ export class PlTreeState { } public getResourceSignature(rid: ResourceId): ResourceSignature | undefined { - return this.resources.get(rid)?.resourceSignature; + return this.resources.get(rid.id)?.resourceSignature; } public entry(rid: ResourceId = this.root): PlTreeEntry { diff --git a/lib/node/pl-tree/src/test_utils.ts b/lib/node/pl-tree/src/test_utils.ts index c79cfac549..774e858152 100644 --- a/lib/node/pl-tree/src/test_utils.ts +++ b/lib/node/pl-tree/src/test_utils.ts @@ -6,7 +6,7 @@ import type { ResourceId, ResourceType, } from "@milaboratories/pl-client"; -import { NullResourceId } from "@milaboratories/pl-client"; +import { NullResourceId, bigintToResourceId } from "@milaboratories/pl-client"; import type { ExtendedResourceData } from "./state"; export const TestRootType1: ResourceType = { @@ -100,7 +100,7 @@ export const TestErrorResourceState2: Omit type: TestErrorResourceType1, }; -export const TestDynamicRootId1 = 1000001n as ResourceId; +export const TestDynamicRootId1 = bigintToResourceId(1000001n); export const TestDynamicRootState1: Omit = { ...InitialStructuralResourceState, inputsLocked: true, @@ -110,7 +110,7 @@ export const TestDynamicRootState1: Omit = { id: TestDynamicRootId1, }; -export const TestDynamicRootId2 = 1000002n as ResourceId; +export const TestDynamicRootId2 = bigintToResourceId(1000002n); export const TestDynamicRootState2: Omit = { ...InitialStructuralResourceState, inputsLocked: true, From 6db30bbc256a4ccefc384f3b25a3badf36b80ec4 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 15:58:31 +0200 Subject: [PATCH 26/27] MILAB-5815: ref: resource id (cleanup) [drafr] --- lib/node/pl-client/src/core/client.ts | 24 +++++++------------ lib/node/pl-client/src/core/transaction.ts | 11 +-------- .../pl-client/src/core/type_conversion.ts | 8 +------ lib/node/pl-client/src/core/types.ts | 15 ++---------- lib/node/pl-drivers/src/clients/download.ts | 6 ++--- lib/node/pl-drivers/src/clients/logs.ts | 12 +++++----- lib/node/pl-drivers/src/clients/ls_api.ts | 4 ++-- lib/node/pl-drivers/src/clients/progress.ts | 6 ++--- lib/node/pl-drivers/src/clients/upload.ts | 24 +++++++++---------- .../drivers/helpers/download_remote_handle.ts | 5 ++-- .../src/drivers/helpers/logs_handle.ts | 5 ++-- .../src/drivers/helpers/ls_storage_entry.ts | 3 +-- lib/node/pl-drivers/src/drivers/ls.ts | 24 ++++++++----------- lib/node/pl-tree/src/accessors.ts | 3 --- lib/node/pl-tree/src/snapshot.ts | 3 +-- lib/node/pl-tree/src/state.ts | 5 +--- 16 files changed, 56 insertions(+), 102 deletions(-) diff --git a/lib/node/pl-client/src/core/client.ts b/lib/node/pl-client/src/core/client.ts index a4e8a2d197..677a062e43 100644 --- a/lib/node/pl-client/src/core/client.ts +++ b/lib/node/pl-client/src/core/client.ts @@ -4,7 +4,7 @@ import { LLPlClient } from "./ll_client"; import type { AnyResourceRef, SignatureResolver } from "./transaction"; import { PlTransaction, toGlobalResourceId, TxCommitConflict } from "./transaction"; import { createHash } from "node:crypto"; -import type { GlobalResourceId, OptionalResourceId, ResourceId, ResourceSignature } from "./types"; +import type { GlobalResourceId, OptionalResourceId, ResourceId } from "./types"; import { bigintToResourceId, ensureResourceIdNotNull, @@ -98,9 +98,6 @@ export class PlClient { /** Resource data cache, to minimize redundant data rereading from remote db */ private readonly resourceDataCache: LRUCache; - /** Root signature (user's actual root, used as default color proof) */ - private _rootSignature?: ResourceSignature; - private constructor( configOrAddress: PlClientConfig | string, auth: AuthOps, @@ -237,13 +234,14 @@ export class PlClient { // Try ListUserResources first (new backend, gRPC only) let rootFromServer: ResourceId | undefined; - let rootSignature: ResourceSignature | undefined; try { const responses = await this._ll.listUserResources({ limit: 1 }); for (const msg of responses) { if (msg.entry.oneofKind === "userRoot") { - rootFromServer = bigintToResourceId(msg.entry.userRoot.resourceId); - rootSignature = toResourceSignature(msg.entry.userRoot.resourceSignature); + rootFromServer = bigintToResourceId( + msg.entry.userRoot.resourceId, + toResourceSignature(msg.entry.userRoot.resourceSignature), + ); break; } } @@ -253,10 +251,6 @@ export class PlClient { } if (rootFromServer !== undefined) { - if (rootSignature && rootSignature.length > 0) { - this._rootSignature = rootSignature; - } - // New path: server created/returned the root if (this.conf.alternativeRoot === undefined) { this._clientRoot = rootFromServer; @@ -279,8 +273,8 @@ export class PlClient { return await altRoot.globalId; }, - rootSignature !== undefined - ? { signatureResolver: (id) => (id === rootFromServer ? rootSignature : undefined) } + rootFromServer.signature !== undefined + ? { signatureResolver: (id) => (id.id === rootFromServer!.id ? rootFromServer!.signature : undefined) } : undefined, ); } @@ -363,8 +357,8 @@ export class PlClient { // Auto-set default color proof so that resource creation (write TXs) // and name lookups (read TXs) carry the correct access color. - if (!isNullResourceId(clientRoot) && this._rootSignature) { - tx.setDefaultColor(this._rootSignature); + if (!isNullResourceId(clientRoot) && clientRoot.signature && writable) { + tx.setDefaultColor(clientRoot.signature); } let ok = false; diff --git a/lib/node/pl-client/src/core/transaction.ts b/lib/node/pl-client/src/core/transaction.ts index e417fbeb10..77cc3a779f 100644 --- a/lib/node/pl-client/src/core/transaction.ts +++ b/lib/node/pl-client/src/core/transaction.ts @@ -350,11 +350,6 @@ export class PlTransaction { return { errorResourceId: resourceId, errorResourceSignature: resourceSignature }; } - private storeSignaturesFromResourceData(_result: BasicResourceData | ResourceData): void { - // Signatures are now embedded in ResourceId objects (via protoToResource/protoToField). - // Local ID signatures are stored directly in createResource. - } - /** Set default color proof for subsequent resource creation requests */ public setDefaultColor(colorProof: ColorProof): void { this.defaultColorProof = colorProof; @@ -476,11 +471,7 @@ export class PlTransaction { loadFields, }, }, - (r) => { - const result = protoToResource(notEmpty(r.resourceGetSingleton.resource)); - this.storeSignaturesFromResourceData(result); - return result; - }, + (r) => protoToResource(notEmpty(r.resourceGetSingleton.resource)), ); } diff --git a/lib/node/pl-client/src/core/type_conversion.ts b/lib/node/pl-client/src/core/type_conversion.ts index ee3de3ce08..58d84b540b 100644 --- a/lib/node/pl-client/src/core/type_conversion.ts +++ b/lib/node/pl-client/src/core/type_conversion.ts @@ -35,9 +35,8 @@ function protoIdToOptionalResourceId(id: bigint, signature?: Uint8Array): Option /** Throws "native" pl not found error, if resource is marked as deleted. */ export function protoToResource(proto: Resource): ResourceData { if (resourceIsDeleted(proto)) throwPlNotFoundError("resource deleted"); - const resourceSignature = toResourceSignature(proto.resourceSignature); return { - id: { id: proto.resourceId as GlobalResourceId, signature: resourceSignature }, + id: { id: proto.resourceId as GlobalResourceId, signature: toResourceSignature(proto.resourceSignature) }, originalResourceId: protoIdToOptionalResourceId(proto.originalResourceId), type: notEmpty(proto.type), data: proto.data, @@ -47,7 +46,6 @@ export function protoToResource(proto: Resource): ResourceData { kind: protoToResourceKind(proto.kind), error: protoToError(proto), final: proto.isFinal, - resourceSignature, fields: proto.fields?.filter((f) => f.id!.fieldName !== ResourceErrorField).map(protoToField), }; } @@ -72,8 +70,6 @@ function protoToError(proto: Resource): OptionalResourceId { } export function protoToField(proto: Field): FieldData { - const valueSignature = toResourceSignature(proto.valueSignature); - const errorSignature = toResourceSignature(proto.errorSignature); return { name: notEmpty(proto.id?.fieldName), type: protoToFieldType(proto.type), @@ -81,8 +77,6 @@ export function protoToField(proto: Field): FieldData { value: protoIdToOptionalResourceId(proto.value, proto.valueSignature), error: protoIdToOptionalResourceId(proto.error, proto.errorSignature), valueIsFinal: proto.valueIsFinal, - valueSignature, - errorSignature, }; } diff --git a/lib/node/pl-client/src/core/types.ts b/lib/node/pl-client/src/core/types.ts index c4f0112a4b..0b26fde357 100644 --- a/lib/node/pl-client/src/core/types.ts +++ b/lib/node/pl-client/src/core/types.ts @@ -7,9 +7,6 @@ type BrandResourceId = bigint & { [__resource_id_type__]: B }; /** Opaque authorization signature attached to a resource. */ declare const __resource_signature_type__: unique symbol; export type ResourceSignature = Uint8Array & { readonly [__resource_signature_type__]: true }; - -export type ResourceId = SignedResourceId - /** Null resource id */ export type NullResourceId = BrandResourceId<"null">; @@ -24,6 +21,8 @@ export type SignedResourceId = { signature?: ResourceSignature } +export type ResourceId = SignedResourceId + /** Any non-null resource id */ export type AnyResourceId = ResourceId | LocalResourceId; @@ -122,9 +121,6 @@ export type BasicResourceData = { /** This value is derived from resource state by the server and can be used as * a robust criteria to determine resource is in final state. */ readonly final: boolean; - - /** Signature for this resource, used for authorization in subsequent requests. */ - readonly resourceSignature?: ResourceSignature; }; export function extractBasicResourceData(rd: ResourceData): BasicResourceData { @@ -139,7 +135,6 @@ export function extractBasicResourceData(rd: ResourceData): BasicResourceData { outputsLocked, resourceReady, final, - resourceSignature, } = rd; return { id, @@ -152,7 +147,6 @@ export function extractBasicResourceData(rd: ResourceData): BasicResourceData { outputsLocked, resourceReady, final, - resourceSignature, }; } @@ -177,11 +171,6 @@ export type FieldData = { /** True if value the fields points to is in final state. */ readonly valueIsFinal: boolean; - - /** Signature for the value resource, inheriting parent resource's color. */ - readonly valueSignature?: ResourceSignature; - /** Signature for the error resource, inheriting parent resource's color. */ - readonly errorSignature?: ResourceSignature; }; // diff --git a/lib/node/pl-drivers/src/clients/download.ts b/lib/node/pl-drivers/src/clients/download.ts index abf83f7772..ac933466ec 100644 --- a/lib/node/pl-drivers/src/clients/download.ts +++ b/lib/node/pl-drivers/src/clients/download.ts @@ -130,7 +130,7 @@ export class ClientDownload { } private async grpcGetDownloadUrl( - { id, type, resourceSignature }: ResourceInfo, + { id, type }: ResourceInfo, options?: RpcOptions, signal?: AbortSignal, ): Promise { @@ -140,7 +140,7 @@ export class ClientDownload { const client = this.wire.get(); if (client instanceof DownloadClient) { return await client.getDownloadURL( - { resourceId: id.id, resourceSignature, isInternalUse: false }, + { resourceId: id.id, resourceSignature: id.signature, isInternalUse: false }, addRTypeToMetadata(type, withAbort), ).response; } else { @@ -148,7 +148,7 @@ export class ClientDownload { await client.POST("/v1/get-download-url", { body: { resourceId: id.id.toString(), - resourceSignature: signatureToBase64(resourceSignature), + resourceSignature: signatureToBase64(id.signature), isInternalUse: false, }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/clients/logs.ts b/lib/node/pl-drivers/src/clients/logs.ts index c69ef8bc3b..85f8a68d06 100644 --- a/lib/node/pl-drivers/src/clients/logs.ts +++ b/lib/node/pl-drivers/src/clients/logs.ts @@ -42,7 +42,7 @@ export class ClientLogs { * the new offset * and the total size of the (currently existing) file. */ public async lastLines( - { id: rId, type: rType, resourceSignature: rSig }: ResourceInfo, + { id: rId, type: rType }: ResourceInfo, lineCount: number, offsetBytes: bigint = 0n, // if 0n, then start from the end. searchStr?: string, @@ -54,7 +54,7 @@ export class ClientLogs { await client.lastLines( { resourceId: rId.id, - resourceSignature: rSig, + resourceSignature: rId.signature, lineCount: lineCount, offset: offsetBytes, search: searchStr, @@ -68,7 +68,7 @@ export class ClientLogs { await client.POST("/v1/last-lines", { body: { resourceId: rId.id.toString(), - resourceSignature: signatureToBase64(rSig), + resourceSignature: signatureToBase64(rId.signature), lineCount: lineCount, offset: offsetBytes.toString(), search: searchStr ?? "", @@ -89,7 +89,7 @@ export class ClientLogs { * the new offset * and the total size of the (currently existing) file. */ public async readText( - { id: rId, type: rType, resourceSignature: rSig }: ResourceInfo, + { id: rId, type: rType }: ResourceInfo, lineCount: number, offsetBytes: bigint = 0n, // if 0n, then start from the beginning. searchStr?: string, @@ -102,7 +102,7 @@ export class ClientLogs { await client.readText( { resourceId: rId.id, - resourceSignature: rSig, + resourceSignature: rId.signature, readLimit: BigInt(lineCount), offset: offsetBytes, search: searchStr, @@ -116,7 +116,7 @@ export class ClientLogs { await client.POST("/v1/read/text", { body: { resourceId: rId.id.toString(), - resourceSignature: signatureToBase64(rSig), + resourceSignature: signatureToBase64(rId.signature), readLimit: lineCount.toString(), offset: offsetBytes.toString(), search: searchStr ?? "", diff --git a/lib/node/pl-drivers/src/clients/ls_api.ts b/lib/node/pl-drivers/src/clients/ls_api.ts index 6db4dcf117..fdfe2ad5e1 100644 --- a/lib/node/pl-drivers/src/clients/ls_api.ts +++ b/lib/node/pl-drivers/src/clients/ls_api.ts @@ -50,7 +50,7 @@ export class ClientLs { return await client.list( { resourceId: rInfo.id.id, - resourceSignature: rInfo.resourceSignature, + resourceSignature: rInfo.id.signature, location: path, }, addRTypeToMetadata(rInfo.type, options), @@ -60,7 +60,7 @@ export class ClientLs { await client.POST("/v1/list", { body: { resourceId: rInfo.id.id.toString(), - resourceSignature: signatureToBase64(rInfo.resourceSignature), + resourceSignature: signatureToBase64(rInfo.id.signature), location: path, }, headers: { ...createRTypeRoutingHeader(rInfo.type) }, diff --git a/lib/node/pl-drivers/src/clients/progress.ts b/lib/node/pl-drivers/src/clients/progress.ts index cb9f482216..c877ac8a29 100644 --- a/lib/node/pl-drivers/src/clients/progress.ts +++ b/lib/node/pl-drivers/src/clients/progress.ts @@ -56,7 +56,7 @@ export class ClientProgress { /** getStatus gets a progress status by given rId and rType. */ async getStatus( - { id, type, resourceSignature }: ResourceInfo, + { id, type }: ResourceInfo, options?: RpcOptions, ): Promise { const client = this.wire.get(); @@ -66,7 +66,7 @@ export class ClientProgress { report = notEmpty( ( await client.getStatus( - { resourceId: id.id, resourceSignature }, + { resourceId: id.id, resourceSignature: id.signature }, addRTypeToMetadata(type, options), ).response ).report, @@ -76,7 +76,7 @@ export class ClientProgress { await client.POST("/v1/get-progress", { body: { resourceId: id.id.toString(), - resourceSignature: signatureToBase64(resourceSignature), + resourceSignature: signatureToBase64(id.signature), }, headers: { ...createRTypeRoutingHeader(type) }, }) diff --git a/lib/node/pl-drivers/src/clients/upload.ts b/lib/node/pl-drivers/src/clients/upload.ts index 6efb937845..f96631974f 100644 --- a/lib/node/pl-drivers/src/clients/upload.ts +++ b/lib/node/pl-drivers/src/clients/upload.ts @@ -75,7 +75,7 @@ export class ClientUpload { close() {} public async initUpload( - { id, type, resourceSignature }: ResourceInfo, + { id, type }: ResourceInfo, options?: RpcOptions, ): Promise<{ overall: bigint; @@ -87,7 +87,7 @@ export class ClientUpload { if (client instanceof UploadClient) { const init = ( - await client.init({ resourceId: id.id, resourceSignature }, addRTypeToMetadata(type, options)) + await client.init({ resourceId: id.id, resourceSignature: id.signature }, addRTypeToMetadata(type, options)) ).response; return { @@ -102,7 +102,7 @@ export class ClientUpload { await client.POST("/v1/upload/init", { body: { resourceId: id.id.toString(), - resourceSignature: signatureToBase64(resourceSignature), + resourceSignature: signatureToBase64(id.signature), }, headers: { ...createRTypeRoutingHeader(type) }, }) @@ -117,7 +117,7 @@ export class ClientUpload { } public async partUpload( - { id, type, resourceSignature }: ResourceInfo, + { id, type }: ResourceInfo, path: string, expectedMTimeUnix: bigint, partNumber: bigint, @@ -134,7 +134,7 @@ export class ClientUpload { await client.getPartURL( { resourceId: id.id, - resourceSignature, + resourceSignature: id.signature, partNumber, uploadedPartSize: 0n, isInternalUse: false, @@ -148,7 +148,7 @@ export class ClientUpload { await client.POST("/v1/upload/get-part-url", { body: { resourceId: id.id.toString(), - resourceSignature: signatureToBase64(resourceSignature), + resourceSignature: signatureToBase64(id.signature), partNumber: partNumber.toString(), uploadedPartSize: "0", isInternalUse: false, @@ -220,7 +220,7 @@ export class ClientUpload { } await this.updateProgress( - { id, type, resourceSignature }, + { id, type }, BigInt(info.chunkEnd - info.chunkStart), options, ); @@ -233,7 +233,7 @@ export class ClientUpload { await client.finalize( { resourceId: info.id.id, - resourceSignature: info.resourceSignature, + resourceSignature: info.id.signature, checksumAlgorithm: UploadAPI_ChecksumAlgorithm.UNSPECIFIED, checksum: new Uint8Array(0), }, @@ -243,7 +243,7 @@ export class ClientUpload { await client.POST("/v1/upload/finalize", { body: { resourceId: info.id.id.toString(), - resourceSignature: signatureToBase64(info.resourceSignature), + resourceSignature: signatureToBase64(info.id.signature), checksumAlgorithm: 0, checksum: "", }, @@ -266,7 +266,7 @@ export class ClientUpload { } private async updateProgress( - { id, type, resourceSignature }: ResourceInfo, + { id, type }: ResourceInfo, bytesProcessed: bigint, options?: RpcOptions, ): Promise { @@ -276,7 +276,7 @@ export class ClientUpload { await client.updateProgress( { resourceId: id.id, - resourceSignature, + resourceSignature: id.signature, bytesProcessed, }, addRTypeToMetadata(type, options), @@ -287,7 +287,7 @@ export class ClientUpload { await client.POST("/v1/upload/update-progress", { body: { resourceId: id.id.toString(), - resourceSignature: signatureToBase64(resourceSignature), + resourceSignature: signatureToBase64(id.signature), bytesProcessed: bytesProcessed.toString(), }, headers: { ...createRTypeRoutingHeader(type) }, diff --git a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts index 678e56c777..a8b2397dee 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/download_remote_handle.ts @@ -20,7 +20,7 @@ export function newRemoteHandle( signer: Signer, ): RemoteBlobHandle { let content = `${rInfo.type.name}/${rInfo.type.version}/${rInfo.id.id}/${getSize(rInfo)}`; - const sigStr = signatureToBase64Url(rInfo.resourceSignature); + const sigStr = signatureToBase64Url(rInfo.id.signature); if (sigStr) { content += `/${sigStr}`; } @@ -51,9 +51,8 @@ export function parseRemoteHandle( return { info: { - id: bigintToResourceId(BigInt(resourceId)), + id: bigintToResourceId(BigInt(resourceId), resourceSig ? base64UrlToSignature(resourceSig) : undefined), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: base64UrlToSignature(resourceSig) } : {}), }, size: Number(size), }; diff --git a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts index b485ffc1fe..5f6fe5836e 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/logs_handle.ts @@ -10,7 +10,7 @@ import { } from "@milaboratories/pl-client"; export function newLogHandle(live: boolean, rInfo: ResourceInfo): sdk.AnyLogHandle { - const sigStr = signatureToBase64Url(rInfo.resourceSignature); + const sigStr = signatureToBase64Url(rInfo.id.signature); const resSig = sigStr ? `/${sigStr}` : ""; if (live) { return `log+live://log/${rInfo.type.name}/${rInfo.type.version}/${rInfo.id.id}${resSig}` as sdk.LiveLogHandle; @@ -52,8 +52,7 @@ export function getResourceInfoFromLogHandle(handle: sdk.AnyLogHandle): Resource const { resourceType, resourceVersion, resourceId, resourceSig } = parsed.groups!; return { - id: bigintToResourceId(BigInt(resourceId)), + id: bigintToResourceId(BigInt(resourceId), resourceSig ? base64UrlToSignature(resourceSig) : undefined), type: { name: resourceType, version: resourceVersion }, - ...(resourceSig ? { resourceSignature: base64UrlToSignature(resourceSig) } : {}), }; } diff --git a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts index 5a8379ede7..3a3a2ca08c 100644 --- a/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts +++ b/lib/node/pl-drivers/src/drivers/helpers/ls_storage_entry.ts @@ -1,5 +1,5 @@ import type * as sdk from "@milaboratories/pl-model-common"; -import type { ResourceId, ResourceSignature, ResourceType } from "@milaboratories/pl-client"; +import type { ResourceId, ResourceType } from "@milaboratories/pl-client"; import { bigintToResourceId } from "@milaboratories/pl-client"; import { assertNever } from "@milaboratories/ts-helpers"; @@ -60,7 +60,6 @@ export type RemoteStorageHandleData = { name: string; id: ResourceId; type: ResourceType; - resourceSignature?: ResourceSignature; }; const remoteHandleRegex = /^remote:\/\/(?.*)\/(?.*)$/; diff --git a/lib/node/pl-drivers/src/drivers/ls.ts b/lib/node/pl-drivers/src/drivers/ls.ts index fac813d36c..b217443522 100644 --- a/lib/node/pl-drivers/src/drivers/ls.ts +++ b/lib/node/pl-drivers/src/drivers/ls.ts @@ -1,4 +1,4 @@ -import type { PlClient, ResourceData, ResourceId, ResourceSignature } from "@milaboratories/pl-client"; +import type { PlClient, ResourceData, ResourceId } from "@milaboratories/pl-client"; import { isNotNullResourceId } from "@milaboratories/pl-client"; import type * as sdk from "@milaboratories/pl-model-common"; import type { @@ -31,7 +31,6 @@ import { import type { LocalStorageProjection, VirtualLocalStorageSpec } from "./types"; import { DefaultVirtualLocalStorages } from "./virtual_storages"; -type StorageEntry = { id: ResourceId; signature?: ResourceSignature }; /** * Extends public and safe SDK's driver API with methods used internally in the middle @@ -68,8 +67,8 @@ export class LsDriver implements InternalLsDriver { private constructor( private readonly logger: MiLogger, private readonly lsClient: ClientLs, - /** Pl storage id, to storage entry (resource id + signature). */ - private readonly storageIdToResourceId: Record, + /** Pl storage id, to resource id (with embedded signature). */ + private readonly storageIdToResourceId: Record, private readonly signer: Signer, /** Virtual storages by name */ private readonly virtualStoragesMap: Map, @@ -194,9 +193,9 @@ export class LsDriver implements InternalLsDriver { initialFullPath: s.initialPath, })); - const otherStorages = Object.entries(this.storageIdToResourceId!).map(([storageId, entry]) => ({ + const otherStorages = Object.entries(this.storageIdToResourceId!).map(([storageId, resourceId]) => ({ name: storageId, - handle: createRemoteStorageHandle(storageId, entry.id), + handle: createRemoteStorageHandle(storageId, resourceId), initialFullPath: "", // we don't have any additional information from where to start browsing remote storages isInitialPathHome: false, })); @@ -210,11 +209,11 @@ export class LsDriver implements InternalLsDriver { return [...virtualStorages, ...noRoot]; } - /** Enrich parsed storage data with resource signature stored alongside its ID */ + /** Enrich parsed storage data with the signature embedded in the stored ResourceId */ private withSignature(storageData: StorageHandleData): StorageHandleData { if (storageData.isRemote) { const sig = this.storageIdToResourceId[storageData.name]?.signature; - if (sig) return { ...storageData, resourceSignature: sig }; + if (sig) return { ...storageData, id: { ...storageData.id, signature: sig } }; } return storageData; } @@ -334,7 +333,7 @@ export class LsDriver implements InternalLsDriver { } } -async function doGetAvailableStorageIds(client: PlClient): Promise> { +async function doGetAvailableStorageIds(client: PlClient): Promise> { return client.withReadTx("GetAvailableStorageIds", async (tx) => { const lsProviderId = await tx.getResourceByName("LSProvider"); const provider = await tx.getResourceData(lsProviderId, true); @@ -343,13 +342,10 @@ async function doGetAvailableStorageIds(client: PlClient): Promise { +function providerToStorageIds(provider: ResourceData): Record { return Object.fromEntries( provider.fields .filter((f) => f.type == "Dynamic" && isNotNullResourceId(f.value)) - .map((f) => [ - f.name.substring("storage/".length), - { id: f.value as ResourceId, signature: f.valueSignature }, - ]), + .map((f) => [f.name.substring("storage/".length), f.value as ResourceId]), ); } diff --git a/lib/node/pl-tree/src/accessors.ts b/lib/node/pl-tree/src/accessors.ts index 6fbcbebc63..4724c5608d 100644 --- a/lib/node/pl-tree/src/accessors.ts +++ b/lib/node/pl-tree/src/accessors.ts @@ -7,7 +7,6 @@ import type { } from "@milaboratories/computable"; import type { ResourceId, - ResourceSignature, ResourceType, OptionalResourceId, } from "@milaboratories/pl-client"; @@ -157,7 +156,6 @@ export class PlTreeEntryAccessor { export type ResourceInfo = { readonly id: ResourceId; readonly type: ResourceType; - readonly resourceSignature?: ResourceSignature; }; /** @@ -205,7 +203,6 @@ export class PlTreeNodeAccessor { return { id: this.id, type: this.resourceType, - resourceSignature: this.resource.resourceSignature, }; } diff --git a/lib/node/pl-tree/src/snapshot.ts b/lib/node/pl-tree/src/snapshot.ts index 69905249b9..9509305db6 100644 --- a/lib/node/pl-tree/src/snapshot.ts +++ b/lib/node/pl-tree/src/snapshot.ts @@ -1,4 +1,4 @@ -import type { ResourceId, ResourceSignature, ResourceType } from "@milaboratories/pl-client"; +import type { ResourceId, ResourceType } from "@milaboratories/pl-client"; import type { Optional, Writable } from "utility-types"; import type { ZodType, z } from "zod"; import type { PlTreeNodeAccessor } from "./accessors"; @@ -18,7 +18,6 @@ export type ResourceSnapshot< > = { readonly id: ResourceId; readonly type: ResourceType; - readonly resourceSignature?: ResourceSignature; readonly data: Data; readonly fields: Fields; readonly kv: KV; diff --git a/lib/node/pl-tree/src/state.ts b/lib/node/pl-tree/src/state.ts index 50d4f83a18..8740499696 100644 --- a/lib/node/pl-tree/src/state.ts +++ b/lib/node/pl-tree/src/state.ts @@ -107,7 +107,6 @@ export class PlTreeResource implements ResourceDataWithFinalState { kvChangedPerKey? = new KeyedChangeSource(); readonly id: ResourceId; - readonly resourceSignature?: ResourceSignature; originalResourceId: OptionalResourceId; readonly kind: ResourceKind; @@ -140,7 +139,6 @@ export class PlTreeResource implements ResourceDataWithFinalState { this.outputsLocked = initialState.outputsLocked; this.resourceReady = initialState.resourceReady; this.finalFlag = initialState.final; - this.resourceSignature = initialState.resourceSignature; this.logger = logger; } @@ -353,7 +351,6 @@ export class PlTreeResource implements ResourceDataWithFinalState { error: this.error, originalResourceId: this.originalResourceId, final: this.finalFlag, - resourceSignature: this.resourceSignature, }; } @@ -799,7 +796,7 @@ export class PlTreeState { } public getResourceSignature(rid: ResourceId): ResourceSignature | undefined { - return this.resources.get(rid.id)?.resourceSignature; + return this.resources.get(rid.id)?.id.signature; } public entry(rid: ResourceId = this.root): PlTreeEntry { From 1a8c22cbeaebe9348a1061a39f8d7785d45ee020 Mon Sep 17 00:00:00 2001 From: Roman Fiskov Date: Thu, 9 Apr 2026 16:09:37 +0200 Subject: [PATCH 27/27] MILAB-5815: fix: test bigint -> resourceId conversion --- lib/node/pl-tree/src/snapshot.test.ts | 26 ++++----- lib/node/pl-tree/src/state.test.ts | 82 +++++++++++++-------------- 2 files changed, 50 insertions(+), 58 deletions(-) diff --git a/lib/node/pl-tree/src/snapshot.test.ts b/lib/node/pl-tree/src/snapshot.test.ts index 19da642848..cfc9a5f3a8 100644 --- a/lib/node/pl-tree/src/snapshot.test.ts +++ b/lib/node/pl-tree/src/snapshot.test.ts @@ -1,6 +1,6 @@ import { expect, test } from "vitest"; import { Computable } from "@milaboratories/computable"; -import { DefaultFinalResourceDataPredicate, ResourceId } from "@milaboratories/pl-client"; +import { bigintToResourceId, DefaultFinalResourceDataPredicate } from "@milaboratories/pl-client"; import { z } from "zod"; import { InferSnapshot, makeResourceSnapshot, rsSchema } from "./snapshot"; import { PlTreeState } from "./state"; @@ -38,16 +38,16 @@ test("simple snapshot test", async () => { }); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], data: new TextEncoder().encode(`{"jf": 0}`), }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), }, ]); @@ -58,8 +58,8 @@ test("simple snapshot test", async () => { tree.updateFromResourceData([ { ...TestValueResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], data: new TextEncoder().encode(`{"jf": 0}`), kv: [{ key: "thekey", value: Buffer.from('"thevalue"') }], }, @@ -67,13 +67,13 @@ test("simple snapshot test", async () => { expect(c1.isChanged()).toBeTruthy(); expect(await c1.getValue()).toMatchObject({ - id: rid(1n), + id: bigintToResourceId(1n), type: TestStructuralResourceType1, data: { jf: 0, }, fields: { - b: rid(2n), + b: bigintToResourceId(2n), c: undefined, }, kv: { @@ -85,8 +85,8 @@ test("simple snapshot test", async () => { tree.updateFromResourceData([ { ...TestValueResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], data: new TextEncoder().encode(`{"jf": 0}`), kv: [{ key: "thekey", value: Buffer.from("123") }], // thekey type changed to number (invalid accordig to zod schema) }, @@ -96,7 +96,3 @@ test("simple snapshot test", async () => { expect((await c1.getValueOrError()).type).toStrictEqual("error"); expect(c1.isChanged()).toBeFalsy(); }); - -function rid(id: bigint): ResourceId { - return id as ResourceId; -} diff --git a/lib/node/pl-tree/src/state.test.ts b/lib/node/pl-tree/src/state.test.ts index 655e73e2d0..30aa5b0888 100644 --- a/lib/node/pl-tree/src/state.test.ts +++ b/lib/node/pl-tree/src/state.test.ts @@ -1,9 +1,9 @@ import { expect, test } from "vitest"; import { Computable } from "@milaboratories/computable"; import { + bigintToResourceId, DefaultFinalResourceDataPredicate, NullResourceId, - ResourceId, } from "@milaboratories/pl-client"; import { isPlTreeEntry, isPlTreeEntryAccessor, isPlTreeNodeAccessor } from "./accessors"; import { PlTreeState } from "./state"; @@ -18,10 +18,6 @@ import { TestValueResourceState1, } from "./test_utils"; -function rid(id: bigint): ResourceId { - return id as ResourceId; -} - test("simple tree test 1", async () => { const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate); const entry = tree.entry(); @@ -54,11 +50,11 @@ test("simple tree test 1", async () => { expect(c1.isChanged()).toBeFalsy(); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(rid(1n)))] }, - { ...TestStructuralResourceState1, id: rid(rid(1n)), fields: [iField("b", rid(rid(2n)))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, + { ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [iField("b", bigintToResourceId(2n))] }, { ...TestValueResourceState1, - id: rid(rid(2n)), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); @@ -85,11 +81,11 @@ test("simple tree kv test", async () => { expect(c1.isChanged()).toBeFalsy(); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(rid(1n)))] }, - { ...TestStructuralResourceState1, id: rid(rid(1n)), fields: [iField("b", rid(rid(2n)))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, + { ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [iField("b", bigintToResourceId(2n))] }, { ...TestValueResourceState1, - id: rid(rid(2n)), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); @@ -101,7 +97,7 @@ test("simple tree kv test", async () => { tree.updateFromResourceData([ { ...TestValueResourceState1, - id: rid(rid(2n)), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), kv: [{ key: "thekey", value: Buffer.from("thevalue") }], }, @@ -114,7 +110,7 @@ test("simple tree kv test", async () => { tree.updateFromResourceData([ { ...TestValueResourceState1, - id: rid(rid(2n)), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), kv: [], }, @@ -143,11 +139,11 @@ test("partial tree update", async () => { expect(c1.isChanged()).toBeFalsy(); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] }, - { ...TestStructuralResourceState1, id: rid(1n), fields: [dField("b", rid(2n))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, + { ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [dField("b", bigintToResourceId(2n))] }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); @@ -155,7 +151,7 @@ test("partial tree update", async () => { expect(await c1.getValue()).toStrictEqual("Test1"); expect(c1.isChanged()).toBeFalsy(); - tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: rid(1n), fields: [] }]); + tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [] }]); expect(c1.isChanged()).toBeTruthy(); expect(await c1.getValue()).toBeUndefined(); expect(c1.isChanged()).toBeFalsy(); @@ -172,10 +168,10 @@ test("resource error", async () => { expect(c1.isChanged()).toBeFalsy(); tree.updateFromResourceData([ - { ...TestDynamicRootState1, error: rid(7n), fields: [] }, + { ...TestDynamicRootState1, error: bigintToResourceId(7n), fields: [] }, { ...TestErrorResourceState2, - id: rid(7n), + id: bigintToResourceId(7n), data: Buffer.from('"error"'), fields: [], }, @@ -197,11 +193,11 @@ test("field error", async () => { tree.updateFromResourceData([ { ...TestDynamicRootState1, - fields: [dField("b", NullResourceId, rid(7n))], + fields: [dField("b", NullResourceId, bigintToResourceId(7n))], }, { ...TestErrorResourceState2, - id: rid(7n), + id: bigintToResourceId(7n), data: Buffer.from('"error"'), fields: [], }, @@ -214,17 +210,17 @@ test("exception - deletion of input field", () => { const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] }, - { ...TestStructuralResourceState1, id: rid(1n), fields: [iField("b", rid(2n))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, + { ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [iField("b", bigintToResourceId(2n))] }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); expect(() => - tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: rid(1n), fields: [] }]), + tree.updateFromResourceData([{ ...TestStructuralResourceState1, id: bigintToResourceId(1n), fields: [] }]), ).toThrow(/removal of Input field/); }); @@ -232,16 +228,16 @@ test("exception - addition of input field", () => { const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], ...ResourceReady, }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); @@ -250,8 +246,8 @@ test("exception - addition of input field", () => { tree.updateFromResourceData([ { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n)), iField("df")], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n)), iField("df")], ...ResourceReady, }, ]), @@ -265,17 +261,17 @@ test("exception - ready without locks 1", () => { tree.updateFromResourceData([ { ...TestDynamicRootState1, - fields: [dField("b"), dField("a", rid(1n))], + fields: [dField("b"), dField("a", bigintToResourceId(1n))], }, { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], resourceReady: true, }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]), @@ -286,15 +282,15 @@ test("exception - ready without locks 2", () => { const tree = new PlTreeState(TestDynamicRootId1, DefaultFinalResourceDataPredicate); tree.updateFromResourceData([ - { ...TestDynamicRootState1, fields: [dField("b"), dField("a", rid(1n))] }, + { ...TestDynamicRootState1, fields: [dField("b"), dField("a", bigintToResourceId(1n))] }, { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]); @@ -303,17 +299,17 @@ test("exception - ready without locks 2", () => { tree.updateFromResourceData([ { ...TestDynamicRootState1, - fields: [dField("b"), dField("a", rid(1n))], + fields: [dField("b"), dField("a", bigintToResourceId(1n))], }, { ...TestStructuralResourceState1, - id: rid(1n), - fields: [iField("b", rid(2n))], + id: bigintToResourceId(1n), + fields: [iField("b", bigintToResourceId(2n))], resourceReady: true, }, { ...TestValueResourceState1, - id: rid(2n), + id: bigintToResourceId(2n), data: new TextEncoder().encode("Test1"), }, ]),