From 489d13f8d41da7e495e49008900358e9d6a8ef32 Mon Sep 17 00:00:00 2001 From: ssanjay1 <67482244+ssanjay1@users.noreply.github.com> Date: Tue, 23 Jul 2024 09:39:42 -0400 Subject: [PATCH] Timeseries (#435) * fix response in codegen * start timeseries work * more progress: * more progress * get tests working * rest of timeseries * fix index files * remove some commented out code * self review cleanup * refactor duration mappping * remove unused code * review comments * add changeset plus one minor revision * modify report --- .changeset/smooth-lemons-own.md | 9 + etc/client.api.report.api.md | 93 ++++++++-- packages/client.api/src/OsdkObjectFrom.ts | 11 +- .../client.api/src/groupby/GroupByClause.ts | 23 +-- packages/client.api/src/index.ts | 7 + .../client.api/src/mapping/DurationMapping.ts | 47 +++++ .../src/mapping/PropertyValueMapping.ts | 9 +- .../client.api/src/object/FetchPageResult.ts | 11 +- .../client.api/src/timeseries/timeseries.ts | 75 ++++++++ .../client/src/__unstable/UnstableClient.ts | 4 +- .../client/src/createTimeseriesProperty.ts | 166 ++++++++++++++++++ .../object/convertWireToOsdkObjects.test.ts | 4 +- .../src/object/convertWireToOsdkObjects.ts | 6 +- .../createOsdkInterface.ts | 4 - .../createOsdkObject.ts | 18 +- packages/client/src/object/object.test.ts | 13 +- packages/client/src/object/timeseries.test.ts | 130 ++++++++++++++ .../client/src/objectSet/ObjectSet.test.ts | 79 ++++----- .../client/src/ontology/OntologyProvider.ts | 2 +- packages/client/src/util/streamutils.ts | 123 +++++++++++++ packages/e2e.generated.catchall/ontology.json | 31 ++++ .../src/generatedNoCheck/Ontology.ts | 3 + .../src/generatedNoCheck/ontology/objects.ts | 1 + .../ontology/objects/DherlihyComplexObject.ts | 54 ++++++ packages/e2e.sandbox.catchall/src/index.ts | 3 + .../src/runTimeseriesTest.ts | 42 +++++ packages/monorepo.cspell/cspell.config.js | 1 + .../monorepo.cspell/dict.normal-dev-words.txt | 3 +- packages/monorepo.cspell/dict.test-words.txt | 3 +- .../monorepo.cspell/dict.wire-api-words.txt | 2 +- .../src/handlers/loadObjectsEndpoints.ts | 52 +++++- .../src/stubs/timeseriesRequests.ts | 17 +- 32 files changed, 941 insertions(+), 105 deletions(-) create mode 100644 .changeset/smooth-lemons-own.md create mode 100644 packages/client.api/src/mapping/DurationMapping.ts create mode 100644 packages/client.api/src/timeseries/timeseries.ts create mode 100644 packages/client/src/createTimeseriesProperty.ts create mode 100644 packages/client/src/object/timeseries.test.ts create mode 100644 packages/client/src/util/streamutils.ts create mode 100644 packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects/DherlihyComplexObject.ts create mode 100644 packages/e2e.sandbox.catchall/src/runTimeseriesTest.ts diff --git a/.changeset/smooth-lemons-own.md b/.changeset/smooth-lemons-own.md new file mode 100644 index 000000000..4cf1481da --- /dev/null +++ b/.changeset/smooth-lemons-own.md @@ -0,0 +1,9 @@ +--- +"@osdk/e2e.generated.catchall": minor +"@osdk/e2e.sandbox.catchall": minor +"@osdk/shared.test": minor +"@osdk/client.api": minor +"@osdk/client": minor +--- + +Add support for timeseries in 2.0 syntax. diff --git a/etc/client.api.report.api.md b/etc/client.api.report.api.md index 8cb8ea24b..cab4e57b4 100644 --- a/etc/client.api.report.api.md +++ b/etc/client.api.report.api.md @@ -178,7 +178,7 @@ export interface BaseObjectSet { // Warning: (ae-incompatible-release-tags) The symbol "ConvertProps" is marked as @public, but its signature references "UnionIfTrue" which is marked as @internal // // @public -export type ConvertProps | InterfaceDefinition, TO extends ValidToFrom, P extends string = "$all"> = TO extends FROM ? P : TO extends ObjectTypeDefinition ? ((UnionIfTrue][P extends "$all" ? (keyof FROM["properties"] extends keyof TO["interfaceMap"][ApiNameAsString] ? keyof FROM["properties"] : never) : DropDollarOptions

], P extends "$notStrict" ? true : false, "$notStrict">)) : UnionIfTrue ? P extends "$all" ? "$all" : FROM extends ObjectTypeDefinition ? DropDollarOptions

extends keyof FROM["inverseInterfaceMap"][ApiNameAsString] ? FROM["inverseInterfaceMap"][ApiNameAsString][DropDollarOptions

] : never : never : never, P extends "$notStrict" ? true : false, "$notStrict">; +export type ConvertProps | InterfaceDefinition, TO extends ValidToFrom, P extends string = "$all"> = TO extends FROM ? P : TO extends ObjectTypeDefinition ? ((UnionIfTrue[ApiNameAsString][P extends "$all" ? (keyof FROM["properties"] extends NonNullable[ApiNameAsString] ? keyof FROM["properties"] : never) : DropDollarOptions

], P extends "$notStrict" ? true : false, "$notStrict">)) : UnionIfTrue ? P extends "$all" ? "$all" : FROM extends ObjectTypeDefinition ? DropDollarOptions

extends keyof NonNullable[ApiNameAsString] ? NonNullable[ApiNameAsString][DropDollarOptions

] : never : never : never, P extends "$notStrict" ? true : false, "$notStrict">; // @public export interface DataValueClientToWire { @@ -307,6 +307,8 @@ export const DistanceUnitMapping: { // @public (undocumented) export const DurationMapping: { + quarter: "QUARTERS"; + quarters: "QUARTERS"; sec: "SECONDS"; seconds: "SECONDS"; min: "MINUTES"; @@ -327,8 +329,6 @@ export const DurationMapping: { yr: "YEARS"; year: "YEARS"; years: "YEARS"; - quarter: "QUARTERS"; - quarters: "QUARTERS"; }; // @public (undocumented) @@ -578,13 +578,13 @@ export interface PropertyValueClientToWire { // (undocumented) marking: string; // (undocumented) - numericTimeseries: unknown; + numericTimeseries: TimeSeriesProperty; // (undocumented) short: number; // (undocumented) string: string; // (undocumented) - stringTimeseries: unknown; + stringTimeseries: TimeSeriesProperty; // (undocumented) timestamp: string; } @@ -616,13 +616,13 @@ export interface PropertyValueWireToClient { // (undocumented) marking: string; // (undocumented) - numericTimeseries: unknown; + numericTimeseries: TimeSeriesProperty; // (undocumented) short: number; // (undocumented) string: string; // (undocumented) - stringTimeseries: unknown; + stringTimeseries: TimeSeriesProperty; // (undocumented) timestamp: string; } @@ -682,6 +682,79 @@ RespectNullability // @public (undocumented) export type StringAggregateOption = "approximateDistinct"; +// @public (undocumented) +export const TimeseriesDurationMapping: { + sec: "SECONDS"; + seconds: "SECONDS"; + min: "MINUTES"; + minute: "MINUTES"; + minutes: "MINUTES"; + hr: "HOURS"; + hrs: "HOURS"; + hour: "HOURS"; + hours: "HOURS"; + day: "DAYS"; + days: "DAYS"; + wk: "WEEKS"; + week: "WEEKS"; + weeks: "WEEKS"; + mos: "MONTHS"; + month: "MONTHS"; + months: "MONTHS"; + yr: "YEARS"; + year: "YEARS"; + years: "YEARS"; + ms: "MILLISECONDS"; + milliseconds: "MILLISECONDS"; +}; + +// @public (undocumented) +export interface TimeSeriesPoint { + // (undocumented) + time: string; + // (undocumented) + value: T; +} + +// @public (undocumented) +export interface TimeSeriesProperty { + // (undocumented) + asyncIterPoints(query: TimeSeriesQuery): AsyncGenerator>; + // (undocumented) + getAllPoints(query: TimeSeriesQuery): Promise>>; + // (undocumented) + getFirstPoint(): Promise>; + // (undocumented) + getLastPoint(): Promise>; +} + +// @public (undocumented) +export type TimeSeriesQuery = { + $before: number; + $unit: keyof typeof TimeseriesDurationMapping; + $after?: never; + $startTime?: never; + $endTime?: never; +} | { + $after: number; + $unit: keyof typeof TimeseriesDurationMapping; + $before?: never; + $startTime?: never; + $endTime?: never; +} | { + $startTime: string; + $endTime?: string; + $before?: never; + $after?: never; + $unit?: never; +} | { + $startTime?: string; + $endTime: string; + $before?: never; + $after?: never; + $unit?: never; +}; + // Warning: (ae-internal-missing-underscore) The name "UnionIfFalse" should be prefixed with an underscore because the declaration is marked as @internal // // @internal @@ -690,7 +763,7 @@ export type UnionIfFalse = // Warning: (ae-internal-missing-underscore) The name "UnionIfTrue" should be prefixed with an underscore because the declaration is marked as @internal // // @internal -export type UnionIfTrue = IsNever_2 extends true ? never : UNION_IF_TRUE extends true ? S | E : S; +export type UnionIfTrue = IsNever_2 extends true ? never : UNION_IF_TRUE extends true ? S | E : S; // @public (undocumented) export type UnorderedAggregationClause = { @@ -718,9 +791,9 @@ export type WhereClause> = OrWhe // Warnings were encountered during analysis: // -// src/OsdkObjectFrom.ts:92:4 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen // src/OsdkObjectFrom.ts:93:4 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen -// src/OsdkObjectFrom.ts:149:5 - (ae-forgotten-export) The symbol "UnderlyingProps" needs to be exported by the entry point index.d.ts +// src/OsdkObjectFrom.ts:94:4 - (tsdoc-param-tag-missing-hyphen) The @param block should be followed by a parameter name and then a hyphen +// src/OsdkObjectFrom.ts:150:5 - (ae-forgotten-export) The symbol "UnderlyingProps" needs to be exported by the entry point index.d.ts // (No @packageDocumentation comment for this package) diff --git a/packages/client.api/src/OsdkObjectFrom.ts b/packages/client.api/src/OsdkObjectFrom.ts index 97ceb8ca8..bc203da27 100644 --- a/packages/client.api/src/OsdkObjectFrom.ts +++ b/packages/client.api/src/OsdkObjectFrom.ts @@ -53,10 +53,10 @@ export type ConvertProps< : TO extends ObjectTypeDefinition ? ( ( UnionIfTrue< - TO["interfaceMap"][ApiNameAsString][ + NonNullable[ApiNameAsString][ P extends "$all" ? ( keyof FROM["properties"] extends - keyof TO["interfaceMap"][ApiNameAsString] + NonNullable[ApiNameAsString] ? keyof FROM["properties"] : never ) @@ -70,9 +70,10 @@ export type ConvertProps< : UnionIfTrue< TO extends InterfaceDefinition ? P extends "$all" ? "$all" : FROM extends ObjectTypeDefinition - ? DropDollarOptions

extends keyof FROM["inverseInterfaceMap"][ - ApiNameAsString - ] ? FROM["inverseInterfaceMap"][ApiNameAsString][ + ? DropDollarOptions

extends + keyof NonNullable[ + ApiNameAsString + ] ? NonNullable[ApiNameAsString][ DropDollarOptions

] : never diff --git a/packages/client.api/src/groupby/GroupByClause.ts b/packages/client.api/src/groupby/GroupByClause.ts index bd0cabe8e..788e9b76c 100644 --- a/packages/client.api/src/groupby/GroupByClause.ts +++ b/packages/client.api/src/groupby/GroupByClause.ts @@ -16,6 +16,7 @@ import type { ObjectOrInterfaceDefinition } from "@osdk/api"; import type { AggregatableKeys } from "../aggregate/AggregatableKeys.js"; +import { TimeDurationMapping } from "../mapping/DurationMapping.js"; import type { GroupByMapper } from "./GroupByMapper.js"; export type GroupByClause< @@ -52,28 +53,8 @@ export type TimestampTimeUnits = | "HOURS"; export type DateTimeUnits = "DAYS" | "WEEKS" | "MONTHS" | "YEARS" | "QUARTERS"; - export const DurationMapping = { - "sec": "SECONDS", - "seconds": "SECONDS", - "min": "MINUTES", - "minute": "MINUTES", - "minutes": "MINUTES", - "hr": "HOURS", - "hrs": "HOURS", - "hour": "HOURS", - "hours": "HOURS", - "day": "DAYS", - "days": "DAYS", - "wk": "WEEKS", - "week": "WEEKS", - "weeks": "WEEKS", - "mos": "MONTHS", - "month": "MONTHS", - "months": "MONTHS", - "yr": "YEARS", - "year": "YEARS", - "years": "YEARS", + ...TimeDurationMapping, "quarter": "QUARTERS", "quarters": "QUARTERS", } satisfies Record; diff --git a/packages/client.api/src/index.ts b/packages/client.api/src/index.ts index fd7106a64..b3f80b6e5 100644 --- a/packages/client.api/src/index.ts +++ b/packages/client.api/src/index.ts @@ -119,6 +119,13 @@ export type { QueryReturnType, QuerySignatureFromDef, } from "./queries/Queries.js"; +export type { + TimeSeriesPoint, + TimeSeriesProperty, + TimeSeriesQuery, +} from "./timeseries/timeseries.js"; +export { TimeseriesDurationMapping } from "./timeseries/timeseries.js"; + export type { LinkedType, LinkNames } from "./util/LinkUtils.js"; export type { NOOP } from "./util/NOOP.js"; diff --git a/packages/client.api/src/mapping/DurationMapping.ts b/packages/client.api/src/mapping/DurationMapping.ts new file mode 100644 index 000000000..f940d9a56 --- /dev/null +++ b/packages/client.api/src/mapping/DurationMapping.ts @@ -0,0 +1,47 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +export const TimeDurationMapping = { + "sec": "SECONDS", + "seconds": "SECONDS", + "min": "MINUTES", + "minute": "MINUTES", + "minutes": "MINUTES", + "hr": "HOURS", + "hrs": "HOURS", + "hour": "HOURS", + "hours": "HOURS", + "day": "DAYS", + "days": "DAYS", + "wk": "WEEKS", + "week": "WEEKS", + "weeks": "WEEKS", + "mos": "MONTHS", + "month": "MONTHS", + "months": "MONTHS", + "yr": "YEARS", + "year": "YEARS", + "years": "YEARS", +} satisfies Record< + string, + | "YEARS" + | "MONTHS" + | "WEEKS" + | "DAYS" + | "HOURS" + | "MINUTES" + | "SECONDS" +>; diff --git a/packages/client.api/src/mapping/PropertyValueMapping.ts b/packages/client.api/src/mapping/PropertyValueMapping.ts index eb19cf0a8..8d0bb4b5b 100644 --- a/packages/client.api/src/mapping/PropertyValueMapping.ts +++ b/packages/client.api/src/mapping/PropertyValueMapping.ts @@ -15,6 +15,7 @@ */ import type { Attachment, AttachmentUpload } from "../object/Attachment.js"; +import type { TimeSeriesProperty } from "../timeseries/timeseries.js"; /** * Map from the PropertyDefinition type to the typescript type that we return @@ -36,8 +37,8 @@ export interface PropertyValueWireToClient { string: string; timestamp: string; - numericTimeseries: unknown; - stringTimeseries: unknown; + numericTimeseries: TimeSeriesProperty; + stringTimeseries: TimeSeriesProperty; } /** @@ -60,6 +61,6 @@ export interface PropertyValueClientToWire { string: string; timestamp: string; - numericTimeseries: unknown; - stringTimeseries: unknown; + numericTimeseries: TimeSeriesProperty; + stringTimeseries: TimeSeriesProperty; } diff --git a/packages/client.api/src/object/FetchPageResult.ts b/packages/client.api/src/object/FetchPageResult.ts index 6e43d0960..ffd9ddd64 100644 --- a/packages/client.api/src/object/FetchPageResult.ts +++ b/packages/client.api/src/object/FetchPageResult.ts @@ -36,10 +36,13 @@ export type UnionIfFalse = : S | E; /** @internal exposed for a test */ -export type UnionIfTrue = - IsNever extends true ? never - : UNION_IF_TRUE extends true ? S | E - : S; +export type UnionIfTrue< + S extends string, + UNION_IF_TRUE extends boolean, + E extends string, +> = IsNever extends true ? never + : UNION_IF_TRUE extends true ? S | E + : S; export type FetchPageResult< Q extends ObjectOrInterfaceDefinition, diff --git a/packages/client.api/src/timeseries/timeseries.ts b/packages/client.api/src/timeseries/timeseries.ts new file mode 100644 index 000000000..d48104fad --- /dev/null +++ b/packages/client.api/src/timeseries/timeseries.ts @@ -0,0 +1,75 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { TimeDurationMapping } from "../mapping/DurationMapping.js"; + +export type TimeSeriesQuery = + | { + $before: number; + $unit: keyof typeof TimeseriesDurationMapping; + $after?: never; + $startTime?: never; + $endTime?: never; + } + | { + $after: number; + $unit: keyof typeof TimeseriesDurationMapping; + $before?: never; + $startTime?: never; + $endTime?: never; + } + | { + $startTime: string; + $endTime?: string; + $before?: never; + $after?: never; + $unit?: never; + } + | { + $startTime?: string; + $endTime: string; + $before?: never; + $after?: never; + $unit?: never; + }; + +export type TimeseriesDurationUnits = + | "YEARS" + | "MONTHS" + | "WEEKS" + | "DAYS" + | "HOURS" + | "MINUTES" + | "SECONDS" + | "MILLISECONDS"; + +export const TimeseriesDurationMapping = { + "ms": "MILLISECONDS", + "milliseconds": "MILLISECONDS", + ...TimeDurationMapping, +} satisfies Record; + +export interface TimeSeriesPoint { + time: string; + value: T; +} + +export interface TimeSeriesProperty { + getFirstPoint(): Promise>; + getLastPoint(): Promise>; + getAllPoints(query: TimeSeriesQuery): Promise>>; + asyncIterPoints(query: TimeSeriesQuery): AsyncGenerator>; +} diff --git a/packages/client/src/__unstable/UnstableClient.ts b/packages/client/src/__unstable/UnstableClient.ts index ff61d1f47..f79b8c035 100644 --- a/packages/client/src/__unstable/UnstableClient.ts +++ b/packages/client/src/__unstable/UnstableClient.ts @@ -39,8 +39,8 @@ export interface UnstableClient extends Client { rid: string, ): UNSTABLE_ObjectSet; - __UNSTABLE_getBulkLinks>( - objs: T[], + __UNSTABLE_getBulkLinks( + objs: Osdk[], links: string[], ): AsyncGenerator; } diff --git a/packages/client/src/createTimeseriesProperty.ts b/packages/client/src/createTimeseriesProperty.ts new file mode 100644 index 000000000..20db65a8c --- /dev/null +++ b/packages/client/src/createTimeseriesProperty.ts @@ -0,0 +1,166 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + type Attachment, + TimeseriesDurationMapping, + type TimeSeriesPoint, + type TimeSeriesProperty, + type TimeSeriesQuery, +} from "@osdk/client.api"; +import type { + AbsoluteTimeRange, + RelativeTime, + RelativeTimeRange, + StreamTimeSeriesPointsRequest, + TimeRange, +} from "@osdk/internal.foundry"; +import { Ontologies, OntologiesV2 } from "@osdk/internal.foundry"; +import type { MinimalClient } from "./MinimalClientContext.js"; +import { + iterateReadableStream, + parseStreamedResponse, +} from "./util/streamutils.js"; + +export function createTimeseriesProperty( + client: MinimalClient, + objectApiName: string, + primaryKey: any, + propertyName: string, +): TimeSeriesProperty { + return { + async getFirstPoint() { + return OntologiesV2.OntologyObjectsV2.getFirstPoint( + client, + await client.ontologyRid, + objectApiName, + primaryKey, + propertyName, + ) as Promise>; + }, + async getLastPoint() { + return OntologiesV2.OntologyObjectsV2.getLastPoint( + client, + await client.ontologyRid, + objectApiName, + primaryKey, + propertyName, + ) as Promise>; + }, + async getAllPoints(query: TimeSeriesQuery) { + return getAllTimeSeriesPoints( + client, + objectApiName, + primaryKey, + propertyName, + query, + ); + }, + + asyncIterPoints(query: TimeSeriesQuery) { + return iterateTimeSeriesPoints( + client, + objectApiName, + primaryKey, + propertyName, + query, + ); + }, + }; +} + +async function getAllTimeSeriesPoints( + client: MinimalClient, + objectApiName: string, + primaryKey: any, + propertyName: string, + body: TimeSeriesQuery, +): Promise>> { + const allPoints: Array> = []; + + for await ( + const point of iterateTimeSeriesPoints( + client, + objectApiName, + primaryKey, + propertyName, + body, + ) + ) { + allPoints.push({ + time: point.time, + value: point.value as T, + }); + } + return allPoints; +} + +async function* iterateTimeSeriesPoints( + client: MinimalClient, + objectApiName: string, + primaryKey: any, + propertyName: string, + body: TimeSeriesQuery, +): AsyncGenerator, any, unknown> { + const utf8decoder = new TextDecoder("utf-8"); + + const streamPointsIterator = await OntologiesV2.OntologyObjectsV2 + .streamPoints( + client, + await client.ontologyRid, + objectApiName, + primaryKey, + propertyName, + { range: getTimeRange(body) }, + ); + + const reader = streamPointsIterator.stream().getReader(); + for await ( + const point of parseStreamedResponse(iterateReadableStream(reader)) + ) { + yield { + time: point.time, + value: point.value as T, + }; + } +} + +function getTimeRange(body: TimeSeriesQuery): TimeRange { + if ("$startTime" in body || "$endTime" in body) { + return { + type: "absolute", + startTime: body.$startTime, + endTime: body.$endTime, + }; + } + return body.$before + ? { + type: "relative", + startTime: { + when: "BEFORE", + value: body.$before, + unit: TimeseriesDurationMapping[body.$unit], + }, + } + : { + type: "relative", + endTime: { + when: "AFTER", + value: body.$after!, + unit: TimeseriesDurationMapping[body.$unit], + }, + }; +} diff --git a/packages/client/src/object/convertWireToOsdkObjects.test.ts b/packages/client/src/object/convertWireToOsdkObjects.test.ts index 8146daa93..bb6a961f0 100644 --- a/packages/client/src/object/convertWireToOsdkObjects.test.ts +++ b/packages/client/src/object/convertWireToOsdkObjects.test.ts @@ -167,7 +167,7 @@ describe("convertWireToOsdkObjects", () => { clientCtx, [objectFromWire], undefined, - )) as Osdk[]; + )) as unknown as Osdk[]; expect(obj.fullName).toEqual("Steve"); expect(Object.keys(obj).sort()).toEqual([ @@ -227,7 +227,7 @@ describe("convertWireToOsdkObjects", () => { clientCtx, [objectFromWire], FooInterface.apiName, - )) as Osdk[]; + )) as unknown as Osdk[]; expect(objAsFoo).toMatchInlineSnapshot(` { diff --git a/packages/client/src/object/convertWireToOsdkObjects.ts b/packages/client/src/object/convertWireToOsdkObjects.ts index 32af7a604..01c266372 100644 --- a/packages/client/src/object/convertWireToOsdkObjects.ts +++ b/packages/client/src/object/convertWireToOsdkObjects.ts @@ -19,7 +19,11 @@ import type { ObjectOrInterfaceDefinition, ObjectTypeDefinition, } from "@osdk/api"; -import type { NullabilityAdherence, Osdk } from "@osdk/client.api"; +import type { + ConvertProps, + NullabilityAdherence, + Osdk, +} from "@osdk/client.api"; import type { OntologyObjectV2 } from "@osdk/internal.foundry"; import invariant from "tiny-invariant"; import type { MinimalClient } from "../MinimalClientContext.js"; diff --git a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts index 83ae1e9af..db71c5441 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/createOsdkInterface.ts @@ -49,10 +49,6 @@ export function createOsdkInterface< [InterfaceDefRef]: interfaceDef, }; - if (process.env.TARGET !== "browser") { - Object.setPrototypeOf(interfaceHolder, OsdkCustomInspectPrototype); - } - const handler = handlerCache.get(interfaceDef); const proxy = new Proxy>( diff --git a/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts b/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts index b933df382..350d292c0 100644 --- a/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts +++ b/packages/client/src/object/convertWireToOsdkObjects/createOsdkObject.ts @@ -17,6 +17,7 @@ import type { Osdk } from "@osdk/client.api"; import type { OntologyObjectV2 } from "@osdk/internal.foundry"; import { createAttachmentFromRid } from "../../createAttachmentFromRid.js"; +import { createTimeseriesProperty } from "../../createTimeseriesProperty.js"; import type { MinimalClient } from "../../MinimalClientContext.js"; import type { FetchedObjectTypeDefinition } from "../../ontology/OntologyProvider.js"; import { createClientCache } from "../Cache.js"; @@ -32,13 +33,12 @@ import type { ObjectHolder, ObjectHolderPrototypeOwnProps, } from "./ObjectHolder.js"; -import { OsdkCustomInspectPrototype } from "./OsdkCustomInspectPrototype.js"; import type { PropertyDescriptorRecord } from "./PropertyDescriptorRecord.js"; const objectPrototypeCache = createClientCache( function(client, objectDef: FetchedObjectTypeDefinition) { return Object.create( - process.env.target !== "browser" ? OsdkCustomInspectPrototype : null, + null, { [ObjectDefRef]: { value: objectDef }, [ClientRef]: { value: client }, @@ -112,6 +112,20 @@ export function createOsdkObject< } return createAttachmentFromRid(client, rawValue.rid); } + if ( + propDef.type === "numericTimeseries" + || propDef.type === "stringTimeseries" + ) { + return createTimeseriesProperty< + (typeof propDef)["type"] extends "numericTimeseries" ? number + : string + >( + client, + objectDef.apiName, + target[RawObject][objectDef.primaryKeyApiName as string], + p as string, + ); + } } return rawValue; } diff --git a/packages/client/src/object/object.test.ts b/packages/client/src/object/object.test.ts index ceaa11d03..fc08f358f 100644 --- a/packages/client/src/object/object.test.ts +++ b/packages/client/src/object/object.test.ts @@ -58,7 +58,18 @@ describe("OsdkObject", () => { // we should get the employee we requested const employee = result.data[0]; - expect(employee).toEqual(asV2Object(stubData.employee1)); + expect(employee).toMatchObject({ + "$apiName": "Employee", + "$objectType": "Employee", + "$primaryKey": 50030, + "$title": "John Doe", + "class": "Red", + "employeeId": 50030, + "employeeStatus": expect.anything(), + "fullName": "John Doe", + "office": "NYC", + "startDate": "2019-01-01", + }); employee.startDate; diff --git a/packages/client/src/object/timeseries.test.ts b/packages/client/src/object/timeseries.test.ts new file mode 100644 index 000000000..a7a034121 --- /dev/null +++ b/packages/client/src/object/timeseries.test.ts @@ -0,0 +1,130 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import type { ObjectOrInterfacePropertyKeysFrom2 } from "@osdk/api"; +import type { Osdk, Result, TimeSeriesPoint } from "@osdk/client.api"; +import { isOk } from "@osdk/client.api"; +import { + Employee, + FooInterface, + Ontology as MockOntology, +} from "@osdk/client.test.ontology"; +import { apiServer, stubData } from "@osdk/shared.test"; +import { + afterAll, + beforeAll, + describe, + expect, + expectTypeOf, + it, +} from "vitest"; +import type { InterfaceDefinition } from "../../../api/build/cjs/index.cjs"; +import type { Client } from "../Client.js"; +import { createClient } from "../createClient.js"; + +describe("Timeseries", () => { + let client: Client; + + beforeAll(async () => { + apiServer.listen(); + client = createClient( + "https://stack.palantir.com", + MockOntology.metadata.ontologyRid, + async () => "myAccessToken", + ); + }); + + afterAll(() => { + apiServer.close(); + }); + + it("get first points works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const point = await employee.employeeStatus?.getFirstPoint(); + expect(point?.time).toEqual("2012-02-12"); + expect(point?.value).toEqual(10); + }); + + it("get last points works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const point = await employee.employeeStatus?.getLastPoint(); + expect(point?.time).toEqual("2014-04-14"); + expect(point?.value).toEqual(30); + }); + + it("getAll points with before works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const points = await employee.employeeStatus?.getAllPoints({ + $before: 1, + $unit: "month", + }); + expect(points).toBeDefined(); + expect(points!).toEqual([{ time: "2012-02-12", value: 10 }, { + time: "2013-03-13", + value: 20, + }, { time: "2014-04-14", value: 30 }]); + }); + + it("getAll points with after works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const points = await employee.employeeStatus?.getAllPoints({ + $after: 1, + $unit: "month", + }); + expect(points).toBeDefined(); + expect(points!).toEqual([{ time: "2012-02-12", value: 10 }, { + time: "2014-04-14", + value: 30, + }]); + }); + + it("getAll points with absolute range works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const points = await employee.employeeStatus?.getAllPoints({ + $startTime: "2013-03-12T12:00:00.000Z", + $endTime: "2014-04-14T12:00:00.000Z", + }); + expect(points).toBeDefined(); + expect(points!).toEqual([{ + time: "2013-03-13", + value: 20, + }, { time: "2014-04-14", value: 30 }]); + }); + + it("async iter points with absolute range works", async () => { + const employee = await client(Employee).fetchOne(50030); + expect(employee.$primaryKey).toEqual(50030); + const pointsIter = employee.employeeStatus?.asyncIterPoints({ + $startTime: "2013-03-12T12:00:00.000Z", + $endTime: "2014-04-14T12:00:00.000Z", + }); + + const points: TimeSeriesPoint[] = []; + for await (const point of pointsIter!) { + points.push(point); + } + expect(points).toBeDefined(); + expect(points!).toEqual([{ + time: "2013-03-13", + value: 20, + }, { time: "2014-04-14", value: 30 }]); + }); +}); diff --git a/packages/client/src/objectSet/ObjectSet.test.ts b/packages/client/src/objectSet/ObjectSet.test.ts index 4b26691b8..7dd4210e9 100644 --- a/packages/client/src/objectSet/ObjectSet.test.ts +++ b/packages/client/src/objectSet/ObjectSet.test.ts @@ -111,47 +111,44 @@ describe("ObjectSet", () => { .fetchPage({ $orderBy: { "employeeId": "asc" }, }); - - expect(employees).toMatchInlineSnapshot(` - [ - { - "$apiName": "Employee", - "$objectType": "Employee", - "$primaryKey": 50030, - "$title": "John Doe", - "class": "Red", - "employeeId": 50030, - "employeeStatus": "TimeSeries", - "fullName": "John Doe", - "office": "NYC", - "startDate": "2019-01-01", - }, - { - "$apiName": "Employee", - "$objectType": "Employee", - "$primaryKey": 50031, - "$title": "Jane Doe", - "class": "Blue", - "employeeId": 50031, - "employeeStatus": "TimeSeries", - "fullName": "Jane Doe", - "office": "SEA", - "startDate": "2012-02-12", - }, - { - "$apiName": "Employee", - "$objectType": "Employee", - "$primaryKey": 50032, - "$title": "Jack Smith", - "class": "Red", - "employeeId": 50032, - "employeeStatus": "TimeSeries", - "fullName": "Jack Smith", - "office": "LON", - "startDate": "2015-05-15", - }, - ] - `); + expect(employees).toMatchObject([ + { + $apiName: "Employee", + $objectType: "Employee", + $primaryKey: 50030, + $title: "John Doe", + class: "Red", + employeeId: 50030, + employeeStatus: expect.anything(), + fullName: "John Doe", + office: "NYC", + startDate: "2019-01-01", + }, + { + $apiName: "Employee", + $objectType: "Employee", + $primaryKey: 50031, + $title: "Jane Doe", + class: "Blue", + employeeId: 50031, + employeeStatus: expect.anything(), + fullName: "Jane Doe", + office: "SEA", + startDate: "2012-02-12", + }, + { + $apiName: "Employee", + $objectType: "Employee", + $primaryKey: 50032, + $title: "Jack Smith", + class: "Red", + employeeId: 50032, + employeeStatus: expect.anything(), + fullName: "Jack Smith", + office: "LON", + startDate: "2015-05-15", + }, + ]); }); it("allows fetching by PK from a base object set - fetchOne", async () => { diff --git a/packages/client/src/ontology/OntologyProvider.ts b/packages/client/src/ontology/OntologyProvider.ts index 36cdeec97..ade0694d6 100644 --- a/packages/client/src/ontology/OntologyProvider.ts +++ b/packages/client/src/ontology/OntologyProvider.ts @@ -47,7 +47,7 @@ export interface OntologyProvider { */ getObjectDefinition: ( apiName: string, - ) => Promise>; + ) => Promise>; /** * Returns the current known definition for the interface. diff --git a/packages/client/src/util/streamutils.ts b/packages/client/src/util/streamutils.ts new file mode 100644 index 000000000..8ed3bd169 --- /dev/null +++ b/packages/client/src/util/streamutils.ts @@ -0,0 +1,123 @@ +/* + * Copyright 2023 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +const START_TOKEN = new Uint8Array([123, 34, 100, 97, 116, 97, 34, 58, 91]); // `{"data":[` +const OBJECT_OPEN_CHAR_CODE = 123; // '{' +const OBJECT_CLOSE_CHAR_CODE = 125; // '}' + +export async function* parseStreamedResponse( + asyncIterable: AsyncIterable, +) { + const utf8decoder = new TextDecoder("utf-8"); + + let parsedStart = false; + let prevChunks: Uint8Array[] = []; + let openBracesCount = 0; + + for await (let chunk of asyncIterable) { + // on the first chunk, skip the expected START_TOKEN if we see it + let i = 0; + if (!parsedStart) { + parsedStart = true; + if (startsWith(chunk, START_TOKEN)) { + i = START_TOKEN.length; + } + } + + for (; i < chunk.length; i++) { + // if we aren't currently parsing an object, skip until we find the next object start + while ( + openBracesCount === 0 && chunk[i] !== OBJECT_OPEN_CHAR_CODE + && i < chunk.length + ) { + i++; + } + + // iterate through the chunk looking for the end of the current top level object + let j = i; + for (; j < chunk.length; j++) { + const c = chunk[j]; + if (c === OBJECT_OPEN_CHAR_CODE) { + openBracesCount++; + } else if (c === OBJECT_CLOSE_CHAR_CODE) { + openBracesCount--; + + // found a complete top level object, emit it + if (0 === openBracesCount) { + yield combineAndParse( + utf8decoder, + prevChunks, + chunk.subarray(i, j + 1), + ); + + // if there was a prevChunk, we've consumed it now + prevChunks = []; + + // advance the start index to the final '}' of the current object, + // which lets us start seeking the beginning of the next object + i = j; + break; + } + } + } + + // if we reached the end of our chunk before finding the end of the object + // store off the relevant remainder of our current chunk and go grab the next one + if (j === chunk.length) { + prevChunks.push(chunk.subarray(i)); + break; + } + } + } +} + +function startsWith(a: Uint8Array, b: Uint8Array) { + if (a.length < b.length) { + return false; + } + + for (let i = 0; i < b.length; i++) { + if (a[i] !== b[i]) { + return false; + } + } + + return true; +} + +function combineAndParse( + utf8decoder: TextDecoder, + prev: Uint8Array[], + curr: Uint8Array, +) { + let str = ""; + for (const chunk of prev) { + str += utf8decoder.decode(chunk, { stream: true }); + } + str += utf8decoder.decode(curr); + + return JSON.parse(str); +} + +export async function* iterateReadableStream( + readableStream: ReadableStreamDefaultReader, +) { + let res = await readableStream.read(); + while (!res.done) { + yield res.value; + res = await readableStream.read(); + } +} diff --git a/packages/e2e.generated.catchall/ontology.json b/packages/e2e.generated.catchall/ontology.json index b2d433af4..2b03d7fc4 100644 --- a/packages/e2e.generated.catchall/ontology.json +++ b/packages/e2e.generated.catchall/ontology.json @@ -179,6 +179,37 @@ }, "linkTypes": [] }, + "DherlihyComplexObject": { + "objectType": { + "apiName": "DherlihyComplexObject", + "primaryKey": "id", + "displayName": "Dherlihy Complex Object", + "description": "Dherlihy Complex Object", + "properties": { + "id": { + "dataType": { + "type": "string" + } + }, + "secret": { + "dataType": { + "type": "string" + } + }, + "seriesId": { + "dataType": { + "type": "timeseries", + "itemType": { + "type": "double" + } + } + } + }, + "status": "ACTIVE", + "rid": "ridfordherlihy" + }, + "linkTypes": [] + }, "BuilderDeploymentState": { "objectType": { "apiName": "BuilderDeploymentState", diff --git a/packages/e2e.generated.catchall/src/generatedNoCheck/Ontology.ts b/packages/e2e.generated.catchall/src/generatedNoCheck/Ontology.ts index 970dfbeb1..ded9134ea 100644 --- a/packages/e2e.generated.catchall/src/generatedNoCheck/Ontology.ts +++ b/packages/e2e.generated.catchall/src/generatedNoCheck/Ontology.ts @@ -9,6 +9,7 @@ export interface Ontology extends OntologyDefinition< | 'BoundariesUsState' | 'BuilderDeploymentState' + | 'DherlihyComplexObject' | 'Employee' | 'ObjectTypeWithAllPropertyTypes' | 'Person' @@ -20,6 +21,7 @@ export interface Ontology objects: { BoundariesUsState: Objects.BoundariesUsState; BuilderDeploymentState: Objects.BuilderDeploymentState; + DherlihyComplexObject: Objects.DherlihyComplexObject; Employee: Objects.Employee; ObjectTypeWithAllPropertyTypes: Objects.ObjectTypeWithAllPropertyTypes; Person: Objects.Person; @@ -46,6 +48,7 @@ export const Ontology: Ontology = { objects: { BoundariesUsState: Objects.BoundariesUsState, BuilderDeploymentState: Objects.BuilderDeploymentState, + DherlihyComplexObject: Objects.DherlihyComplexObject, Employee: Objects.Employee, ObjectTypeWithAllPropertyTypes: Objects.ObjectTypeWithAllPropertyTypes, Person: Objects.Person, diff --git a/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects.ts b/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects.ts index 1b7367e9d..41e9ea397 100644 --- a/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects.ts +++ b/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects.ts @@ -1,5 +1,6 @@ export * from './objects/BoundariesUsState.js'; export * from './objects/BuilderDeploymentState.js'; +export * from './objects/DherlihyComplexObject.js'; export * from './objects/Employee.js'; export * from './objects/ObjectTypeWithAllPropertyTypes.js'; export * from './objects/Person.js'; diff --git a/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects/DherlihyComplexObject.ts b/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects/DherlihyComplexObject.ts new file mode 100644 index 000000000..6c09ca020 --- /dev/null +++ b/packages/e2e.generated.catchall/src/generatedNoCheck/ontology/objects/DherlihyComplexObject.ts @@ -0,0 +1,54 @@ +import type { ObjectTypeDefinition, PropertyDef, VersionBound } from '@osdk/api'; +import type { $ExpectedClientVersion } from '../../OntologyMetadata.js'; +import { $osdkMetadata } from '../../OntologyMetadata.js'; + +export interface DherlihyComplexObject + extends ObjectTypeDefinition<'DherlihyComplexObject', DherlihyComplexObject>, + VersionBound<$ExpectedClientVersion> { + osdkMetadata: typeof $osdkMetadata; + description: 'Dherlihy Complex Object'; + links: {}; + primaryKeyApiName: 'id'; + primaryKeyType: 'string'; + properties: { + /** + * (no ontology metadata) + */ + id: PropertyDef<'string', 'non-nullable', 'single'>; + /** + * (no ontology metadata) + */ + secret: PropertyDef<'string', 'nullable', 'single'>; + /** + * (no ontology metadata) + */ + seriesId: PropertyDef<'numericTimeseries', 'nullable', 'single'>; + }; +} + +export const DherlihyComplexObject: DherlihyComplexObject = { + osdkMetadata: $osdkMetadata, + apiName: 'DherlihyComplexObject', + description: 'Dherlihy Complex Object', + links: {}, + primaryKeyApiName: 'id', + primaryKeyType: 'string', + properties: { + id: { + multiplicity: false, + type: 'string', + nullable: false, + }, + secret: { + multiplicity: false, + type: 'string', + nullable: true, + }, + seriesId: { + multiplicity: false, + type: 'numericTimeseries', + nullable: true, + }, + }, + type: 'object', +}; diff --git a/packages/e2e.sandbox.catchall/src/index.ts b/packages/e2e.sandbox.catchall/src/index.ts index 81a120fe1..6c5e1d08e 100644 --- a/packages/e2e.sandbox.catchall/src/index.ts +++ b/packages/e2e.sandbox.catchall/src/index.ts @@ -25,6 +25,7 @@ import { runGeoQueriesTest } from "./runGeoQueriesTest.js"; import { runInterfacesTest } from "./runInterfacesTest.js"; import { runLegacyExamples } from "./runLegacyExamples.js"; import { runSubscriptionsTest } from "./runSubscriptionsTest.js"; +import { runTimeseriesTest } from "./runTimeseriesTest.js"; import { typeChecks } from "./typeChecks.js"; const runOld = false; @@ -61,6 +62,8 @@ async function runTests() { await runAggregationGroupByDatesTest(); if (runOld) await typeChecks(client); + + await runTimeseriesTest(); } catch (e) { console.error(`Caught an error we did not expect, type: ${typeof e}`); console.error(e); diff --git a/packages/e2e.sandbox.catchall/src/runTimeseriesTest.ts b/packages/e2e.sandbox.catchall/src/runTimeseriesTest.ts new file mode 100644 index 000000000..83c15a8a9 --- /dev/null +++ b/packages/e2e.sandbox.catchall/src/runTimeseriesTest.ts @@ -0,0 +1,42 @@ +/* + * Copyright 2024 Palantir Technologies, Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { DherlihyComplexObject } from "@osdk/e2e.generated.catchall"; +import { client } from "./client.js"; + +export async function runTimeseriesTest() { + const result = await client(DherlihyComplexObject).fetchOne("a"); + + console.log(result.id); + + const timeSeriesPoint1 = await result.seriesId?.getAllPoints({ + $before: 2, + $unit: "year", + }); + + console.log(timeSeriesPoint1); + + const result2 = await client(DherlihyComplexObject).fetchOne("b"); + + for await ( + const point of result2.seriesId?.asyncIterPoints({ + $before: 2, + $unit: "year", + })! + ) { + console.log(point); + } +} diff --git a/packages/monorepo.cspell/cspell.config.js b/packages/monorepo.cspell/cspell.config.js index 9dc8a4926..7eeccfb3d 100644 --- a/packages/monorepo.cspell/cspell.config.js +++ b/packages/monorepo.cspell/cspell.config.js @@ -184,6 +184,7 @@ const cspell = { "Downey", "Hemsworth", "underlyings", + "Dherlihy", ], }, ], diff --git a/packages/monorepo.cspell/dict.normal-dev-words.txt b/packages/monorepo.cspell/dict.normal-dev-words.txt index db100f438..6830a425b 100644 --- a/packages/monorepo.cspell/dict.normal-dev-words.txt +++ b/packages/monorepo.cspell/dict.normal-dev-words.txt @@ -13,4 +13,5 @@ uncapitalize unevaluable unioned webapi -Geolocatable \ No newline at end of file +Geolocatable +streamutils \ No newline at end of file diff --git a/packages/monorepo.cspell/dict.test-words.txt b/packages/monorepo.cspell/dict.test-words.txt index a15b1aea1..c5ac110eb 100644 --- a/packages/monorepo.cspell/dict.test-words.txt +++ b/packages/monorepo.cspell/dict.test-words.txt @@ -3,4 +3,5 @@ objectdoesnotexist querydoesnotexist actiondoesnotexit unstubAllEnvs -createSpys \ No newline at end of file +createSpys +Dherlihy \ No newline at end of file diff --git a/packages/monorepo.cspell/dict.wire-api-words.txt b/packages/monorepo.cspell/dict.wire-api-words.txt index da68369dc..4be3800ff 100644 --- a/packages/monorepo.cspell/dict.wire-api-words.txt +++ b/packages/monorepo.cspell/dict.wire-api-words.txt @@ -22,4 +22,4 @@ rulesets schemamigrations SearchArounds typeclasses -workstate \ No newline at end of file +workstate diff --git a/packages/shared.test/src/handlers/loadObjectsEndpoints.ts b/packages/shared.test/src/handlers/loadObjectsEndpoints.ts index 5084c07e8..2f20c58a6 100644 --- a/packages/shared.test/src/handlers/loadObjectsEndpoints.ts +++ b/packages/shared.test/src/handlers/loadObjectsEndpoints.ts @@ -14,6 +14,7 @@ * limitations under the License. */ +import { request } from "@osdk/gateway"; import { executeQueryV2, getAttachment, @@ -33,10 +34,13 @@ import { listOntologies, listOutgoingLinkTypes, listQueryTypesV2, + streamPoints, uploadAttachment, } from "@osdk/gateway/requests"; import type { LinkTypeSide } from "@osdk/gateway/types"; -import type { RequestHandler } from "msw"; +import stableStringify from "json-stable-stringify"; +import type { HttpResponseResolver, PathParams, RequestHandler } from "msw"; +import type { BaseAPIError } from "../BaseError.js"; import { AttachmentNotFoundError, AttachmentSizeExceededLimitError, @@ -75,12 +79,15 @@ import { queryTypes } from "../stubs/queryTypes.js"; import { firstPointRequestHandlers, lastPointRequestHandlers, + streamPointsFrom, + streamPointsRequestHandlers, } from "../stubs/timeseriesRequests.js"; import { areArrayBuffersEqual, pageThroughResponseSearchParams, } from "./endpointUtils.js"; import { getOntology } from "./ontologyMetadataEndpoints.js"; +import type { ExtractBody } from "./util/handleOpenApiCall.js"; import { handleOpenApiCall, OpenApiCallError, @@ -225,7 +232,8 @@ export const loadObjectsEndpoints: Array = [ const firstPointResp = firstPointRequestHandlers[JSON.stringify(pointParams)]; if ( - req.params.ontologyApiName === defaultOntology.apiName + (req.params.ontologyApiName === defaultOntology.apiName + || req.params.ontologyApiName === defaultOntology.rid) && req.params.objectType === employeeObjectType.apiName ) { return firstPointResp; @@ -247,7 +255,8 @@ export const loadObjectsEndpoints: Array = [ const lastPointResp = lastPointRequestHandlers[JSON.stringify(pointParams)]; if ( - req.params.ontologyApiName === "default-ontology" + (req.params.ontologyApiName === defaultOntology.apiName + || req.params.ontologyApiName === defaultOntology.rid) && req.params.objectType === employeeObjectType.apiName ) { return lastPointResp; @@ -256,6 +265,15 @@ export const loadObjectsEndpoints: Array = [ }, ), + /** + * stream points + */ + handleOpenApiCall( + streamPoints, + ["ontologyApiName", "objectType", "primaryKey", "propertyName"], + handleStreamPoints, + ), + /** * Get linkType */ @@ -642,3 +660,31 @@ export const loadObjectsEndpoints: Array = [ throw new OpenApiCallError(404, AttachmentNotFoundError); }), ] as const; + +async function handleStreamPoints( + req: Parameters< + HttpResponseResolver< + PathParams, + | ExtractBody + | Blob + | BaseAPIError + > + >[0], +) { + const requestBody = await req.request.json(); + const streamPointsResp = + streamPointsRequestHandlers[stableStringify(requestBody)]; + if ( + streamPointsResp + && (req.params.ontologyApiName === defaultOntology.apiName + || req.params.ontologyApiName === defaultOntology.rid) + && req.params.objectType === employeeObjectType.apiName + ) { + const blob = new Blob( + [JSON.stringify(streamPointsResp)], + { type: "application/json" }, + ); + return blob; + } + throw new OpenApiCallError(400, InvalidRequest("Invalid request")); +} diff --git a/packages/shared.test/src/stubs/timeseriesRequests.ts b/packages/shared.test/src/stubs/timeseriesRequests.ts index 4ada80737..f3ac0920c 100644 --- a/packages/shared.test/src/stubs/timeseriesRequests.ts +++ b/packages/shared.test/src/stubs/timeseriesRequests.ts @@ -66,6 +66,17 @@ const fromBodyRequest: StreamTimeSeriesPointsRequest = { }, }; +const afterBodyRequest: StreamTimeSeriesPointsRequest = { + range: { + type: "relative", + endTime: { + when: "AFTER", + value: 1, + unit: "MONTHS", + }, + }, +}; + const rangeBodyRequest: StreamTimeSeriesPointsRequest = { range: { type: "absolute", @@ -87,7 +98,10 @@ export const streamPointsRange: StreamTimeSeriesPointsResponse = { data: [timeSeriesPoint2, timeSeriesPoint3], }; export const streamPointsFrom: StreamTimeSeriesPointsResponse = { - data: [timeSeriesPoint1, timeSeriesPoint1, timeSeriesPoint2], + data: [timeSeriesPoint1, timeSeriesPoint2, timeSeriesPoint3], +}; +export const streamPointsAfter: StreamTimeSeriesPointsResponse = { + data: [timeSeriesPoint1, timeSeriesPoint3], }; export const firstPointRequestHandlers: Record = { @@ -109,4 +123,5 @@ export const streamPointsRequestHandlers: Record< [stableStringify(noBodyRequest)]: streamPointsNoBody, [stableStringify(rangeBodyRequest)]: streamPointsRange, [stableStringify(fromBodyRequest)]: streamPointsFrom, + [stableStringify(afterBodyRequest)]: streamPointsAfter, };