Skip to content

Commit 1a36fc8

Browse files
authored
fix: normalize entity query prefix in client.getEntities() (#286)
1 parent f91c8a9 commit 1a36fc8

2 files changed

Lines changed: 97 additions & 3 deletions

File tree

packages/durabletask-js/src/client/client.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import { mapToRecord } from "../utils/tags.util";
3131
import { populateTagsMap } from "../utils/pb-helper.util";
3232
import { EntityInstanceId } from "../entities/entity-instance-id";
3333
import { EntityMetadata, createEntityMetadata, createEntityMetadataWithoutState } from "../entities/entity-metadata";
34-
import { EntityQuery } from "../entities/entity-query";
34+
import { EntityQuery, normalizeInstanceIdPrefix } from "../entities/entity-query";
3535
import { SignalEntityOptions } from "../entities/signal-entity-options";
3636
import {
3737
CleanEntityStorageRequest,
@@ -1103,9 +1103,10 @@ export class TaskHubGrpcClient {
11031103
const req = new pb.QueryEntitiesRequest();
11041104
const protoQuery = new pb.EntityQuery();
11051105

1106-
if (query?.instanceIdStartsWith) {
1106+
const normalizedPrefix = normalizeInstanceIdPrefix(query?.instanceIdStartsWith);
1107+
if (normalizedPrefix) {
11071108
const prefix = new StringValue();
1108-
prefix.setValue(query.instanceIdStartsWith);
1109+
prefix.setValue(normalizedPrefix);
11091110
protoQuery.setInstanceidstartswith(prefix);
11101111
}
11111112

packages/durabletask-js/test/entity-client.spec.ts

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,3 +395,96 @@ describe("EntityInstanceId.fromString", () => {
395395
expect(() => EntityInstanceId.fromString("@onlyname")).toThrow();
396396
});
397397
});
398+
399+
describe("getEntities query normalization", () => {
400+
const { TaskHubGrpcClient } = require("../src");
401+
402+
function getStub(client: InstanceType<typeof TaskHubGrpcClient>): any {
403+
return (client as any)._stub;
404+
}
405+
406+
function mockQueryEntities(
407+
client: InstanceType<typeof TaskHubGrpcClient>,
408+
captureRequest: (req: pb.QueryEntitiesRequest) => void,
409+
): void {
410+
getStub(client).queryEntities = (req: pb.QueryEntitiesRequest, _metadata: any, callback: any) => {
411+
captureRequest(req);
412+
const res = new pb.QueryEntitiesResponse();
413+
callback(null, res);
414+
return {};
415+
};
416+
}
417+
418+
it("should normalize mixed-case instanceIdStartsWith prefix", async () => {
419+
const client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" });
420+
let capturedReq: pb.QueryEntitiesRequest | undefined;
421+
422+
mockQueryEntities(client, (req) => { capturedReq = req; });
423+
424+
const query: EntityQuery = { instanceIdStartsWith: "Counter" };
425+
const pageable = client.getEntities(query);
426+
427+
// Consume the first page to trigger the gRPC call
428+
for await (const _entity of pageable) {
429+
// no-op
430+
}
431+
432+
expect(capturedReq).toBeDefined();
433+
const protoPrefix = capturedReq!.getQuery()?.getInstanceidstartswith()?.getValue();
434+
expect(protoPrefix).toBe("@counter");
435+
});
436+
437+
it("should normalize prefix with name and key separator", async () => {
438+
const client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" });
439+
let capturedReq: pb.QueryEntitiesRequest | undefined;
440+
441+
mockQueryEntities(client, (req) => { capturedReq = req; });
442+
443+
const query: EntityQuery = { instanceIdStartsWith: "Counter@User-123" };
444+
const pageable = client.getEntities(query);
445+
446+
for await (const _entity of pageable) {
447+
// no-op
448+
}
449+
450+
expect(capturedReq).toBeDefined();
451+
const protoPrefix = capturedReq!.getQuery()?.getInstanceidstartswith()?.getValue();
452+
expect(protoPrefix).toBe("@counter@User-123");
453+
});
454+
455+
it("should not set prefix when instanceIdStartsWith is undefined", async () => {
456+
const client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" });
457+
let capturedReq: pb.QueryEntitiesRequest | undefined;
458+
459+
mockQueryEntities(client, (req) => { capturedReq = req; });
460+
461+
const query: EntityQuery = {};
462+
const pageable = client.getEntities(query);
463+
464+
for await (const _entity of pageable) {
465+
// no-op
466+
}
467+
468+
expect(capturedReq).toBeDefined();
469+
const protoPrefix = capturedReq!.getQuery()?.getInstanceidstartswith();
470+
expect(protoPrefix).toBeUndefined();
471+
});
472+
473+
it("should preserve already-normalized prefix", async () => {
474+
const client = new TaskHubGrpcClient({ hostAddress: "localhost:4001" });
475+
let capturedReq: pb.QueryEntitiesRequest | undefined;
476+
477+
mockQueryEntities(client, (req) => { capturedReq = req; });
478+
479+
const query: EntityQuery = { instanceIdStartsWith: "@counter@" };
480+
const pageable = client.getEntities(query);
481+
482+
for await (const _entity of pageable) {
483+
// no-op
484+
}
485+
486+
expect(capturedReq).toBeDefined();
487+
const protoPrefix = capturedReq!.getQuery()?.getInstanceidstartswith()?.getValue();
488+
expect(protoPrefix).toBe("@counter@");
489+
});
490+
});

0 commit comments

Comments
 (0)