diff --git a/sdks/typescript/src/dsl.ts b/sdks/typescript/src/dsl.ts index b5d3201f0..ea7191892 100644 --- a/sdks/typescript/src/dsl.ts +++ b/sdks/typescript/src/dsl.ts @@ -672,6 +672,9 @@ export class StreamBound implements Encodable { readonly payload: unknown, ) {} static literal(value: number | bigint): StreamBound { + if ((typeof value === "number" && value < 0) || (typeof value === "bigint" && value < 0n)) { + throw new TypeError("stream bound literal must be non-negative"); + } const safe = intToJson(value); if (typeof safe === "bigint") { if (safe > BigInt(Number.MAX_SAFE_INTEGER)) throw new TypeError(`stream bound exceeds JavaScript safe integer range: ${safe}`); diff --git a/sdks/typescript/test/basic.test.ts b/sdks/typescript/test/basic.test.ts index 3c92ba907..042e8db27 100644 --- a/sdks/typescript/test/basic.test.ts +++ b/sdks/typescript/test/basic.test.ts @@ -19,6 +19,7 @@ import { RepeatConfig, ShortestPathDirection, SourcePredicate, + StreamBound, VectorDistanceMetric, WhenThen, bytes, @@ -116,6 +117,14 @@ for (const value of [-1, 1.5, 256, "7", true]) { } assert.deepEqual(parsed(PropertyInput.param("limit")), { expr: { param: "limit" } }); assert.deepEqual(parsed(NodeRef.param("node_ids")), { param: "node_ids" }); +assert.deepEqual(parsed(StreamBound.literal(0)), { literal: 0 }); +assert.deepEqual(parsed(StreamBound.literal(BigInt(Number.MAX_SAFE_INTEGER))), { + literal: Number.MAX_SAFE_INTEGER, +}); +for (const value of [-1, -1n, -9_007_199_254_740_993n]) { + assert.throws(() => StreamBound.literal(value), TypeError); +} +assert.throws(() => StreamBound.literal(BigInt(Number.MAX_SAFE_INTEGER) + 1n), TypeError); assert.deepEqual(parsed(QueryParamType.array(QueryParamType.array(QueryParamType.f64()))), { array: { array: "f64" } }); assert.equal(Object.isFrozen(param.array(param.string())), true); assert.equal(Object.isFrozen(QueryParamType.array(QueryParamType.string())), true);