@milaboratories/pl-drivers 1.14.16 → 1.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/ls_api.cjs +1 -0
- package/dist/clients/ls_api.cjs.map +1 -1
- package/dist/clients/ls_api.js +1 -0
- package/dist/clients/ls_api.js.map +1 -1
- package/dist/drivers/helpers/ls_remote_import_handle.cjs +5 -3
- package/dist/drivers/helpers/ls_remote_import_handle.cjs.map +1 -1
- package/dist/drivers/helpers/ls_remote_import_handle.js +5 -3
- package/dist/drivers/helpers/ls_remote_import_handle.js.map +1 -1
- package/dist/drivers/ls.cjs +3 -3
- package/dist/drivers/ls.cjs.map +1 -1
- package/dist/drivers/ls.d.ts +6 -6
- package/dist/drivers/ls.d.ts.map +1 -1
- package/dist/drivers/ls.js +3 -3
- package/dist/drivers/ls.js.map +1 -1
- package/dist/drivers/types.cjs +2 -1
- package/dist/drivers/types.cjs.map +1 -1
- package/dist/drivers/types.d.ts +14 -0
- package/dist/drivers/types.d.ts.map +1 -1
- package/dist/drivers/types.js +2 -1
- package/dist/drivers/types.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.cjs +31 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.cjs.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts +11 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.d.ts.map +1 -1
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.js +31 -0
- package/dist/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.js.map +1 -1
- package/package.json +5 -5
- package/src/clients/ls_api.ts +1 -0
- package/src/drivers/helpers/ls_remote_import_handle.test.ts +46 -0
- package/src/drivers/helpers/ls_remote_import_handle.ts +6 -0
- package/src/drivers/ls.test.ts +138 -2
- package/src/drivers/ls.ts +9 -9
- package/src/drivers/types.ts +5 -0
- package/src/proto-grpc/github.com/milaboratory/pl/controllers/shared/grpc/lsapi/protocol.ts +34 -0
- package/src/proto-rest/lsapi.ts +8 -0
package/src/drivers/ls.test.ts
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { ConsoleLoggerAdapter, HmacSha256Signer } from "@milaboratories/ts-helpers";
|
|
2
|
-
import { LsDriver } from "./ls";
|
|
2
|
+
import { LsDriver, type LsEntryWithFileStats } from "./ls";
|
|
3
3
|
import { TestHelpers } from "@milaboratories/pl-client";
|
|
4
4
|
import * as path from "node:path";
|
|
5
|
-
import { test, expect } from "vitest";
|
|
5
|
+
import { test, expect, describe } from "vitest";
|
|
6
6
|
import { isImportFileHandleIndex, isImportFileHandleUpload } from "@milaboratories/pl-model-common";
|
|
7
|
+
import type { StorageHandle } from "@milaboratories/pl-model-common";
|
|
7
8
|
import * as env from "../test_env";
|
|
9
|
+
import { parseIndexHandle } from "./helpers/ls_remote_import_handle";
|
|
10
|
+
import { createRemoteStorageHandle } from "./helpers/ls_storage_entry";
|
|
8
11
|
|
|
9
12
|
const assetsPath = path.resolve("../../../assets");
|
|
10
13
|
|
|
@@ -141,3 +144,136 @@ test("should ok when get file using local dialog, and read its content", async (
|
|
|
141
144
|
expect(multiResult.files![0]).toStrictEqual(result.file);
|
|
142
145
|
});
|
|
143
146
|
});
|
|
147
|
+
|
|
148
|
+
// Unit tests: verify that LsDriver.listFiles and listRemoteFilesWithFileStats correctly
|
|
149
|
+
// thread additionalInfo from gRPC list items into the index:// handle.
|
|
150
|
+
describe("LsDriver additionalInfo threading", () => {
|
|
151
|
+
const envelope = { uid: "u1", sid: "s1", sig: "sigval", exp: "9999999999", kid: "k1", v: "1" };
|
|
152
|
+
|
|
153
|
+
const storageInfo = {
|
|
154
|
+
storageId: "test-storage",
|
|
155
|
+
storageName: "Test Storage",
|
|
156
|
+
// Signed resource id format "<globalId>|<signatureHex>" — required by asSignedResourceId.
|
|
157
|
+
resourceId: "res-id|deadbeef" as any,
|
|
158
|
+
resourceType: { name: "LS/test-storage", version: "1" },
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
// Builds a minimal LsDriver instance with an injected mock lsClient via private-constructor bypass.
|
|
162
|
+
function makeMockDriver(listResponse: { items: any[]; delimiter: string }): LsDriver {
|
|
163
|
+
const mockLsClient = {
|
|
164
|
+
list: async () => listResponse,
|
|
165
|
+
close: () => {},
|
|
166
|
+
};
|
|
167
|
+
const mockUserResources = {
|
|
168
|
+
getDataLibraries: async () => new Map([[storageInfo.storageId, storageInfo]]),
|
|
169
|
+
};
|
|
170
|
+
const signer = new HmacSha256Signer("test");
|
|
171
|
+
// Bypass private constructor for unit testing only.
|
|
172
|
+
return new (LsDriver as any)(
|
|
173
|
+
new ConsoleLoggerAdapter(),
|
|
174
|
+
mockLsClient,
|
|
175
|
+
mockUserResources,
|
|
176
|
+
signer,
|
|
177
|
+
new Map(),
|
|
178
|
+
new Map(),
|
|
179
|
+
() => Promise.resolve(undefined),
|
|
180
|
+
) as LsDriver;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function makeRemoteHandle(): StorageHandle {
|
|
184
|
+
return createRemoteStorageHandle(storageInfo) as StorageHandle;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
test("listFiles: handle carries additionalInfo envelope from gRPC item", async () => {
|
|
188
|
+
const driver = makeMockDriver({
|
|
189
|
+
delimiter: "/",
|
|
190
|
+
items: [
|
|
191
|
+
{
|
|
192
|
+
name: "file.txt",
|
|
193
|
+
size: 100n,
|
|
194
|
+
isDir: false,
|
|
195
|
+
additionalInfo: envelope,
|
|
196
|
+
fullName: "dir/file.txt",
|
|
197
|
+
directory: "dir/",
|
|
198
|
+
version: "v1",
|
|
199
|
+
},
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
const result = await driver.listFiles(makeRemoteHandle(), "dir/");
|
|
204
|
+
expect(result.entries).toHaveLength(1);
|
|
205
|
+
|
|
206
|
+
const parsed = parseIndexHandle(result.entries[0].handle as any);
|
|
207
|
+
expect(parsed.additionalInfo).toEqual(envelope);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("listFiles: handle has no additionalInfo when item has empty envelope", async () => {
|
|
211
|
+
const driver = makeMockDriver({
|
|
212
|
+
delimiter: "/",
|
|
213
|
+
items: [
|
|
214
|
+
{
|
|
215
|
+
name: "plain.txt",
|
|
216
|
+
size: 50n,
|
|
217
|
+
isDir: false,
|
|
218
|
+
additionalInfo: {},
|
|
219
|
+
fullName: "plain.txt",
|
|
220
|
+
directory: "",
|
|
221
|
+
version: "v1",
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const result = await driver.listFiles(makeRemoteHandle(), "");
|
|
227
|
+
expect(result.entries).toHaveLength(1);
|
|
228
|
+
|
|
229
|
+
const parsed = parseIndexHandle(result.entries[0].handle as any);
|
|
230
|
+
expect(parsed.additionalInfo).toBeUndefined();
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
test("listRemoteFilesWithFileStats: handle carries additionalInfo envelope", async () => {
|
|
234
|
+
const driver = makeMockDriver({
|
|
235
|
+
delimiter: "/",
|
|
236
|
+
items: [
|
|
237
|
+
{
|
|
238
|
+
name: "data.csv",
|
|
239
|
+
size: 200n,
|
|
240
|
+
isDir: false,
|
|
241
|
+
additionalInfo: envelope,
|
|
242
|
+
fullName: "data.csv",
|
|
243
|
+
directory: "",
|
|
244
|
+
version: "v2",
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const result = await driver.listRemoteFilesWithFileStats(makeRemoteHandle(), "");
|
|
250
|
+
expect(result.entries).toHaveLength(1);
|
|
251
|
+
expect((result.entries[0] as LsEntryWithFileStats).size).toBe(200);
|
|
252
|
+
|
|
253
|
+
const parsed = parseIndexHandle(result.entries[0].handle as any);
|
|
254
|
+
expect(parsed.additionalInfo).toEqual(envelope);
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("listRemoteFilesWithFileStats: no additionalInfo when absent in item", async () => {
|
|
258
|
+
const driver = makeMockDriver({
|
|
259
|
+
delimiter: "/",
|
|
260
|
+
items: [
|
|
261
|
+
{
|
|
262
|
+
name: "plain.csv",
|
|
263
|
+
size: 10n,
|
|
264
|
+
isDir: false,
|
|
265
|
+
additionalInfo: {},
|
|
266
|
+
fullName: "plain.csv",
|
|
267
|
+
directory: "",
|
|
268
|
+
version: "v1",
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
const result = await driver.listRemoteFilesWithFileStats(makeRemoteHandle(), "");
|
|
274
|
+
expect(result.entries).toHaveLength(1);
|
|
275
|
+
|
|
276
|
+
const parsed = parseIndexHandle(result.entries[0].handle as any);
|
|
277
|
+
expect(parsed.additionalInfo).toBeUndefined();
|
|
278
|
+
});
|
|
279
|
+
});
|
package/src/drivers/ls.ts
CHANGED
|
@@ -34,18 +34,18 @@ export interface InternalLsDriver extends sdk.LsDriver {
|
|
|
34
34
|
* */
|
|
35
35
|
getLocalFileHandle(localPath: string): Promise<sdk.LocalImportFileHandle>;
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
listRemoteFilesWithFileStats(
|
|
38
38
|
storage: sdk.StorageHandle,
|
|
39
39
|
fullPath: string,
|
|
40
|
-
): Promise<
|
|
40
|
+
): Promise<ListRemoteFilesResultWithFileStats>;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
export type
|
|
43
|
+
export type ListRemoteFilesResultWithFileStats = {
|
|
44
44
|
parent?: string;
|
|
45
|
-
entries:
|
|
45
|
+
entries: LsEntryWithFileStats[];
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
export type
|
|
48
|
+
export type LsEntryWithFileStats = sdk.LsEntry & {
|
|
49
49
|
size: number;
|
|
50
50
|
};
|
|
51
51
|
|
|
@@ -217,7 +217,7 @@ export class LsDriver implements InternalLsDriver {
|
|
|
217
217
|
type: e.isDir ? "dir" : "file",
|
|
218
218
|
name: e.name,
|
|
219
219
|
fullPath: e.fullName,
|
|
220
|
-
handle: createIndexImportHandle(storageData.storageId, e.fullName),
|
|
220
|
+
handle: createIndexImportHandle(storageData.storageId, e.fullName, e.additionalInfo),
|
|
221
221
|
})),
|
|
222
222
|
};
|
|
223
223
|
}
|
|
@@ -249,10 +249,10 @@ export class LsDriver implements InternalLsDriver {
|
|
|
249
249
|
return { entries };
|
|
250
250
|
}
|
|
251
251
|
|
|
252
|
-
public async
|
|
252
|
+
public async listRemoteFilesWithFileStats(
|
|
253
253
|
storageHandle: sdk.StorageHandle,
|
|
254
254
|
fullPath: string,
|
|
255
|
-
): Promise<
|
|
255
|
+
): Promise<ListRemoteFilesResultWithFileStats> {
|
|
256
256
|
const storageData = parseStorageHandle(storageHandle);
|
|
257
257
|
if (!storageData.isRemote) {
|
|
258
258
|
throw new Error(`Storage ${storageData.name} is not remote`);
|
|
@@ -266,7 +266,7 @@ export class LsDriver implements InternalLsDriver {
|
|
|
266
266
|
type: e.isDir ? "dir" : "file",
|
|
267
267
|
name: e.name,
|
|
268
268
|
fullPath: e.fullName,
|
|
269
|
-
handle: createIndexImportHandle(storageData.storageId, e.fullName),
|
|
269
|
+
handle: createIndexImportHandle(storageData.storageId, e.fullName, e.additionalInfo),
|
|
270
270
|
size: Number(e.size),
|
|
271
271
|
})),
|
|
272
272
|
};
|
package/src/drivers/types.ts
CHANGED
|
@@ -54,6 +54,11 @@ export const ImportFileHandleIndexData = z.object({
|
|
|
54
54
|
storageId: z.string(),
|
|
55
55
|
/** Path inside storage */
|
|
56
56
|
path: z.string(),
|
|
57
|
+
/**
|
|
58
|
+
* Federative identity envelope from LsAPI.List.Response.ListItem.additional_info.
|
|
59
|
+
* Absent for non-federative storages (backwards compatible: old handles parse unchanged).
|
|
60
|
+
*/
|
|
61
|
+
additionalInfo: z.record(z.string(), z.string()).optional(),
|
|
57
62
|
});
|
|
58
63
|
export type ImportFileHandleIndexData = z.infer<typeof ImportFileHandleIndexData>;
|
|
59
64
|
|
|
@@ -40,6 +40,16 @@ export interface LsAPI_ListItem {
|
|
|
40
40
|
* @generated from protobuf field: bool is_dir = 3
|
|
41
41
|
*/
|
|
42
42
|
isDir: boolean;
|
|
43
|
+
/**
|
|
44
|
+
* additional_info carries the signed identity envelope for federative storages.
|
|
45
|
+
* KV schema: v=1, path, uid, sid, role, exp, kid, signed, sig.
|
|
46
|
+
* Empty for non-federative storages. Verifiers MUST ignore unknown keys.
|
|
47
|
+
*
|
|
48
|
+
* @generated from protobuf field: map<string, string> additional_info = 8
|
|
49
|
+
*/
|
|
50
|
+
additionalInfo: {
|
|
51
|
+
[key: string]: string;
|
|
52
|
+
};
|
|
43
53
|
/**
|
|
44
54
|
* full_name is the full name of the item, relative to the storage root.
|
|
45
55
|
* It is <directory> + <name>.
|
|
@@ -169,6 +179,7 @@ class LsAPI_ListItem$Type extends MessageType<LsAPI_ListItem> {
|
|
|
169
179
|
{ no: 1, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
170
180
|
{ no: 2, name: "size", kind: "scalar", T: 4 /*ScalarType.UINT64*/, L: 0 /*LongType.BIGINT*/ },
|
|
171
181
|
{ no: 3, name: "is_dir", kind: "scalar", T: 8 /*ScalarType.BOOL*/ },
|
|
182
|
+
{ no: 8, name: "additional_info", kind: "map", K: 9 /*ScalarType.STRING*/, V: { kind: "scalar", T: 9 /*ScalarType.STRING*/ } },
|
|
172
183
|
{ no: 10, name: "full_name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
173
184
|
{ no: 11, name: "directory", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
|
|
174
185
|
{ no: 12, name: "last_modified", kind: "message", T: () => Timestamp },
|
|
@@ -180,6 +191,7 @@ class LsAPI_ListItem$Type extends MessageType<LsAPI_ListItem> {
|
|
|
180
191
|
message.name = "";
|
|
181
192
|
message.size = 0n;
|
|
182
193
|
message.isDir = false;
|
|
194
|
+
message.additionalInfo = {};
|
|
183
195
|
message.fullName = "";
|
|
184
196
|
message.directory = "";
|
|
185
197
|
message.version = "";
|
|
@@ -201,6 +213,9 @@ class LsAPI_ListItem$Type extends MessageType<LsAPI_ListItem> {
|
|
|
201
213
|
case /* bool is_dir */ 3:
|
|
202
214
|
message.isDir = reader.bool();
|
|
203
215
|
break;
|
|
216
|
+
case /* map<string, string> additional_info */ 8:
|
|
217
|
+
this.binaryReadMap8(message.additionalInfo, reader, options);
|
|
218
|
+
break;
|
|
204
219
|
case /* string full_name */ 10:
|
|
205
220
|
message.fullName = reader.string();
|
|
206
221
|
break;
|
|
@@ -224,6 +239,22 @@ class LsAPI_ListItem$Type extends MessageType<LsAPI_ListItem> {
|
|
|
224
239
|
}
|
|
225
240
|
return message;
|
|
226
241
|
}
|
|
242
|
+
private binaryReadMap8(map: LsAPI_ListItem["additionalInfo"], reader: IBinaryReader, options: BinaryReadOptions): void {
|
|
243
|
+
let len = reader.uint32(), end = reader.pos + len, key: keyof LsAPI_ListItem["additionalInfo"] | undefined, val: LsAPI_ListItem["additionalInfo"][any] | undefined;
|
|
244
|
+
while (reader.pos < end) {
|
|
245
|
+
let [fieldNo, wireType] = reader.tag();
|
|
246
|
+
switch (fieldNo) {
|
|
247
|
+
case 1:
|
|
248
|
+
key = reader.string();
|
|
249
|
+
break;
|
|
250
|
+
case 2:
|
|
251
|
+
val = reader.string();
|
|
252
|
+
break;
|
|
253
|
+
default: throw new globalThis.Error("unknown map entry field for MiLaboratories.Controller.Shared.LsAPI.ListItem.additional_info");
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
map[key ?? ""] = val ?? "";
|
|
257
|
+
}
|
|
227
258
|
internalBinaryWrite(message: LsAPI_ListItem, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter {
|
|
228
259
|
/* string name = 1; */
|
|
229
260
|
if (message.name !== "")
|
|
@@ -234,6 +265,9 @@ class LsAPI_ListItem$Type extends MessageType<LsAPI_ListItem> {
|
|
|
234
265
|
/* bool is_dir = 3; */
|
|
235
266
|
if (message.isDir !== false)
|
|
236
267
|
writer.tag(3, WireType.Varint).bool(message.isDir);
|
|
268
|
+
/* map<string, string> additional_info = 8; */
|
|
269
|
+
for (let k of globalThis.Object.keys(message.additionalInfo))
|
|
270
|
+
writer.tag(8, WireType.LengthDelimited).fork().tag(1, WireType.LengthDelimited).string(k).tag(2, WireType.LengthDelimited).string(message.additionalInfo[k]).join();
|
|
237
271
|
/* string full_name = 10; */
|
|
238
272
|
if (message.fullName !== "")
|
|
239
273
|
writer.tag(10, WireType.LengthDelimited).string(message.fullName);
|
package/src/proto-rest/lsapi.ts
CHANGED
|
@@ -41,6 +41,14 @@ export interface components {
|
|
|
41
41
|
size: string;
|
|
42
42
|
/** @description is_dir is true for an item that can have subitems. */
|
|
43
43
|
isDir: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* @description additional_info carries the signed identity envelope for federative storages.
|
|
46
|
+
* KV schema: v=1, path, uid, sid, role, exp, kid, signed, sig.
|
|
47
|
+
* Empty for non-federative storages. Verifiers MUST ignore unknown keys.
|
|
48
|
+
*/
|
|
49
|
+
additionalInfo?: {
|
|
50
|
+
[key: string]: string;
|
|
51
|
+
};
|
|
44
52
|
/**
|
|
45
53
|
* @description full_name is the full name of the item, relative to the storage root.
|
|
46
54
|
* It is <directory> + <name>.
|