diff --git a/.changeset/plenty-berries-add.md b/.changeset/plenty-berries-add.md new file mode 100644 index 000000000..4031912bc --- /dev/null +++ b/.changeset/plenty-berries-add.md @@ -0,0 +1,10 @@ +--- +"@osdk/legacy-client": minor +"@osdk/shared.test": minor +"@osdk/client.api": minor +"@osdk/generator": minor +"@osdk/client": minor +"@osdk/api": minor +--- + +Add support for queries in 2.0 diff --git a/examples-extra/basic/sdk/src/generatedNoCheck/Ontology.ts b/examples-extra/basic/sdk/src/generatedNoCheck/Ontology.ts index 9db7c132c..970dfbeb1 100644 --- a/examples-extra/basic/sdk/src/generatedNoCheck/Ontology.ts +++ b/examples-extra/basic/sdk/src/generatedNoCheck/Ontology.ts @@ -2,6 +2,7 @@ import type { OntologyDefinition } from '@osdk/api'; import * as Actions from './ontology/actions/index.js'; import * as Interfaces from './ontology/interfaces.js'; import * as Objects from './ontology/objects.js'; +import * as Queries from './ontology/queries/index.js'; import { OntologyMetadata } from './OntologyMetadata.js'; export interface Ontology @@ -32,7 +33,8 @@ export interface Ontology createTodo: typeof Actions.createTodo; }; queries: { - // TODO + getTodoCount: typeof Queries.getTodoCount; + queryTakesAllParameterTypes: typeof Queries.queryTakesAllParameterTypes; }; interfaces: { FooInterface: Interfaces.FooInterface; @@ -57,7 +59,8 @@ export const Ontology: Ontology = { createTodo: Actions.createTodo, }, queries: { - // TODO + getTodoCount: Queries.getTodoCount, + queryTakesAllParameterTypes: Queries.queryTakesAllParameterTypes, }, interfaces: { FooInterface: Interfaces.FooInterface, diff --git a/examples-extra/basic/sdk/src/generatedNoCheck/index.ts b/examples-extra/basic/sdk/src/generatedNoCheck/index.ts index ff3b88e0c..1353972dd 100644 --- a/examples-extra/basic/sdk/src/generatedNoCheck/index.ts +++ b/examples-extra/basic/sdk/src/generatedNoCheck/index.ts @@ -2,3 +2,4 @@ export { Ontology } from './Ontology.js'; export * from './ontology/actions/index.js'; export * from './ontology/interfaces.js'; export * from './ontology/objects.js'; +export * from './ontology/queries/index.js'; diff --git a/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/getTodoCount.ts b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/getTodoCount.ts new file mode 100644 index 000000000..0660f6be9 --- /dev/null +++ b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/getTodoCount.ts @@ -0,0 +1,9 @@ +import { QueryDefinition } from '@osdk/api'; + +export const getTodoCount = { + apiName: 'getTodoCount', + type: 'query', + version: '0.1.2', + parameters: {}, + output: { nullable: false, type: 'integer' }, +} satisfies QueryDefinition<'getTodoCount', never>; diff --git a/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/index.ts b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/index.ts new file mode 100644 index 000000000..6185c146e --- /dev/null +++ b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/index.ts @@ -0,0 +1,2 @@ +export * from './getTodoCount.js'; +export * from './queryTakesAllParameterTypes.js'; diff --git a/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts new file mode 100644 index 000000000..b9f5e158f --- /dev/null +++ b/examples-extra/basic/sdk/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts @@ -0,0 +1,111 @@ +import { QueryDefinition } from '@osdk/api'; +import { Todo } from '../objects.js'; +export const queryTakesAllParameterTypes = { + apiName: 'queryTakesAllParameterTypes', + description: 'description of the query that takes all parameter types', + displayName: 'qTAPT', + type: 'query', + version: 'version', + parameters: { + double: { description: 'a double parameter', nullable: false, type: 'double' }, + float: { nullable: false, type: 'float' }, + integer: { nullable: false, type: 'integer' }, + long: { nullable: false, type: 'long' }, + attachment: { nullable: false, type: 'attachment' }, + boolean: { nullable: false, type: 'boolean' }, + date: { nullable: false, type: 'date' }, + string: { nullable: false, type: 'string' }, + timestamp: { nullable: false, type: 'timestamp' }, + object: { + nullable: false, + object: 'Todo', + type: 'object', + + __OsdkTargetType: Todo, + }, + objectSet: { + nullable: false, + objectSet: 'Todo', + type: 'objectSet', + + __OsdkTargetType: Todo, + }, + array: { description: 'an array of strings', multiplicity: true, nullable: false, type: 'string' }, + set: { + description: 'a set of strings', + nullable: false, + set: { + type: 'string', + nullable: false, + }, + type: 'set', + }, + unionNonNullable: { + description: 'a union of strings and integers', + nullable: false, + type: 'union', + union: [ + { + type: 'string', + nullable: false, + }, + { + type: 'integer', + nullable: false, + }, + ], + }, + unionNullable: { + description: 'a union of strings and integers but its optional', + nullable: true, + type: 'union', + union: [ + { + type: 'string', + nullable: false, + }, + { + type: 'integer', + nullable: false, + }, + ], + }, + struct: { + description: 'a struct with some fields', + nullable: false, + struct: { + name: { + type: 'string', + nullable: false, + }, + id: { + type: 'integer', + nullable: false, + }, + }, + type: 'struct', + }, + twoDimensionalAggregation: { + nullable: false, + twoDimensionalAggregation: { + keyType: 'string', + valueType: 'double', + }, + type: 'twoDimensionalAggregation', + }, + threeDimensionalAggregation: { + nullable: false, + threeDimensionalAggregation: { + keyType: 'range', + keySubtype: 'date', + valueType: { + keyType: 'range', + keySubtype: 'timestamp', + valueType: 'date', + }, + }, + type: 'threeDimensionalAggregation', + }, + }, + output: { nullable: false, type: 'string' }, +} satisfies QueryDefinition<'queryTakesAllParameterTypes', 'Todo'>; diff --git a/examples-extra/docs_example/src/generatedNoCheck/Ontology.ts b/examples-extra/docs_example/src/generatedNoCheck/Ontology.ts index 5c0b7f40a..084fec1bd 100644 --- a/examples-extra/docs_example/src/generatedNoCheck/Ontology.ts +++ b/examples-extra/docs_example/src/generatedNoCheck/Ontology.ts @@ -20,9 +20,7 @@ export interface Ontology extends OntologyDefinition<'Employee' | 'equipment' | promoteEmployee: typeof Actions.promoteEmployee; promoteEmployeeObject: typeof Actions.promoteEmployeeObject; }; - queries: { - // TODO - }; + queries: {}; interfaces: {}; } @@ -43,8 +41,6 @@ export const Ontology: Ontology = { promoteEmployee: Actions.promoteEmployee, promoteEmployeeObject: Actions.promoteEmployeeObject, }, - queries: { - // TODO - }, + queries: {}, interfaces: {}, }; diff --git a/examples-extra/docs_example/src/generatedNoCheck/index.ts b/examples-extra/docs_example/src/generatedNoCheck/index.ts index eb6457683..3f96ac903 100644 --- a/examples-extra/docs_example/src/generatedNoCheck/index.ts +++ b/examples-extra/docs_example/src/generatedNoCheck/index.ts @@ -2,3 +2,4 @@ export { Ontology } from './Ontology'; export * from './ontology/actions/index'; export * from './ontology/interfaces'; export * from './ontology/objects'; +export * from './ontology/queries/index'; diff --git a/examples-extra/docs_example/src/generatedNoCheck/ontology/queries/index.ts b/examples-extra/docs_example/src/generatedNoCheck/ontology/queries/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/examples-extra/docs_example/src/generatedNoCheck/ontology/queries/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/examples-extra/one_dot_one/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts b/examples-extra/one_dot_one/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts index 41487210e..ce3d1c03b 100644 --- a/examples-extra/one_dot_one/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts +++ b/examples-extra/one_dot_one/src/generatedNoCheck/ontology/queries/queryTakesAllParameterTypes.ts @@ -47,6 +47,7 @@ export const queryTakesAllParameterTypes = { twoDimensionalAggregation: { type: 'twoDimensionalAggregation', twoDimensionalAggregation: { keyType: 'string', valueType: 'double' }, + nullable: false, }, threeDimensionalAggregation: { type: 'threeDimensionalAggregation', @@ -55,6 +56,7 @@ export const queryTakesAllParameterTypes = { keySubtype: 'date', valueType: { keyType: 'range', keySubtype: 'timestamp', valueType: 'date' }, }, + nullable: false, }, }, output: { type: 'string', nullable: false }, diff --git a/examples-extra/todoapp/src/generatedNoCheck2/Ontology.ts b/examples-extra/todoapp/src/generatedNoCheck2/Ontology.ts index 273162c2f..32afa1451 100644 --- a/examples-extra/todoapp/src/generatedNoCheck2/Ontology.ts +++ b/examples-extra/todoapp/src/generatedNoCheck2/Ontology.ts @@ -12,9 +12,7 @@ export interface Ontology extends OntologyDefinition<'Todo'> { completeTodo: typeof Actions.completeTodo; createTodo: typeof Actions.createTodo; }; - queries: { - // TODO - }; + queries: {}; interfaces: {}; } @@ -27,8 +25,6 @@ export const Ontology: Ontology = { completeTodo: Actions.completeTodo, createTodo: Actions.createTodo, }, - queries: { - // TODO - }, + queries: {}, interfaces: {}, }; diff --git a/examples-extra/todoapp/src/generatedNoCheck2/index.ts b/examples-extra/todoapp/src/generatedNoCheck2/index.ts index eb6457683..3f96ac903 100644 --- a/examples-extra/todoapp/src/generatedNoCheck2/index.ts +++ b/examples-extra/todoapp/src/generatedNoCheck2/index.ts @@ -2,3 +2,4 @@ export { Ontology } from './Ontology'; export * from './ontology/actions/index'; export * from './ontology/interfaces'; export * from './ontology/objects'; +export * from './ontology/queries/index'; diff --git a/examples-extra/todoapp/src/generatedNoCheck2/ontology/queries/index.ts b/examples-extra/todoapp/src/generatedNoCheck2/ontology/queries/index.ts new file mode 100644 index 000000000..cb0ff5c3b --- /dev/null +++ b/examples-extra/todoapp/src/generatedNoCheck2/ontology/queries/index.ts @@ -0,0 +1 @@ +export {}; diff --git a/packages/api/src/ontology/QueryDefinition.ts b/packages/api/src/ontology/QueryDefinition.ts index d5574144f..83bfd86c4 100644 --- a/packages/api/src/ontology/QueryDefinition.ts +++ b/packages/api/src/ontology/QueryDefinition.ts @@ -14,24 +14,37 @@ * limitations under the License. */ -export interface QueryDefinition { +import type { OsdkMetadata } from "../OsdkMetadata.js"; +import type { ObjectTypeDefinition } from "./ObjectTypeDefinition.js"; + +export interface QueryDefinition< + Q extends string, + K extends string, +> { type: "query"; apiName: Q; description?: string; displayName?: string; version: string; - parameters: Record>; - output: QueryDataTypeDefinition; + parameters: Record>; + output: QueryDataTypeDefinition; + osdkMetadata?: OsdkMetadata; } -export type QueryParameterDefinition = { +export type QueryParameterDefinition< + K extends string, + T_Target extends ObjectTypeDefinition = never, +> = { description?: string; -} & QueryDataTypeDefinition; +} & QueryDataTypeDefinition; -export type QueryDataTypeDefinition = +export type QueryDataTypeDefinition< + K extends string, + T_Target extends ObjectTypeDefinition = never, +> = | PrimitiveDataType - | ObjectQueryDataType - | ObjectSetQueryDataType + | ObjectQueryDataType + | ObjectSetQueryDataType | SetQueryDataType | UnionQueryDataType | StructQueryDataType @@ -59,16 +72,20 @@ export type PrimitiveDataType< Q extends WireQueryDataTypes = WireQueryDataTypes, > = BaseQueryDataTypeDefinition; -export interface ObjectQueryDataType - extends BaseQueryDataTypeDefinition<"object"> -{ +export interface ObjectQueryDataType< + K extends string, + T_Target extends ObjectTypeDefinition = never, +> extends BaseQueryDataTypeDefinition<"object"> { object: K; + __OsdkTargetType?: T_Target; } -export interface ObjectSetQueryDataType - extends BaseQueryDataTypeDefinition<"objectSet"> -{ +export interface ObjectSetQueryDataType< + K extends string, + T_Target extends ObjectTypeDefinition = never, +> extends BaseQueryDataTypeDefinition<"objectSet"> { objectSet: K; + __OsdkTargetType?: T_Target; } export interface SetQueryDataType diff --git a/packages/client.api/etc/client.api.report.api.md b/packages/client.api/etc/client.api.report.api.md index c2e7f24d1..6675d053b 100644 --- a/packages/client.api/etc/client.api.report.api.md +++ b/packages/client.api/etc/client.api.report.api.md @@ -13,13 +13,17 @@ import type { IsNever as IsNever_2 } from 'type-fest'; import type { ObjectActionDataType } from '@osdk/api'; import type { ObjectOrInterfaceDefinition } from '@osdk/api'; import type { ObjectOrInterfacePropertyKeysFrom2 } from '@osdk/api'; +import type { ObjectQueryDataType } from '@osdk/api'; import type { ObjectSetActionDataType } from '@osdk/api'; +import type { ObjectSetQueryDataType } from '@osdk/api'; import type { ObjectTypeDefinition } from '@osdk/api'; import type { ObjectTypeLinkDefinition } from '@osdk/api'; import type { ObjectTypeLinkKeysFrom2 } from '@osdk/api'; import type { ObjectTypePropertyDefinition } from '@osdk/api'; import type { Point } from 'geojson'; import type { Polygon } from 'geojson'; +import type { QueryDataTypeDefinition } from '@osdk/api'; +import type { QueryDefinition } from '@osdk/api'; import type { SingleKeyObject } from 'type-fest'; // Warning: (ae-forgotten-export) The symbol "ActionResults" needs to be exported by the entry point index.d.ts @@ -98,6 +102,15 @@ export type AggregationsResults> { // (undocumented) @@ -167,6 +180,104 @@ export interface BaseObjectSet { // @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">; +// @public +export interface DataValueClientToWire { + // (undocumented) + attachment: string | AttachmentUpload; + // (undocumented) + boolean: boolean; + // (undocumented) + byte: number; + // (undocumented) + datetime: string; + // (undocumented) + decimal: string | number; + // (undocumented) + double: number; + // (undocumented) + float: number; + // (undocumented) + integer: number; + // (undocumented) + long: string | number; + // (undocumented) + marking: string; + // (undocumented) + null: null; + // (undocumented) + set: Set; + // (undocumented) + short: number; + // (undocumented) + string: string; + // (undocumented) + struct: Record; + // (undocumented) + threeDimensionalAggregation: { + key: AllowedBucketKeyTypes; + groups: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; + }[]; + // (undocumented) + timestamp: string; + // (undocumented) + twoDimensionalAggregation: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; +} + +// @public +export interface DataValueWireToClient { + // (undocumented) + attachment: Attachment; + // (undocumented) + boolean: boolean; + // (undocumented) + byte: number; + // (undocumented) + datetime: string; + // (undocumented) + decimal: string; + // (undocumented) + double: number; + // (undocumented) + float: number; + // (undocumented) + integer: number; + // (undocumented) + long: string; + // (undocumented) + marking: string; + // (undocumented) + null: null; + // (undocumented) + set: Set; + // (undocumented) + short: number; + // (undocumented) + string: string; + // (undocumented) + struct: Record; + // (undocumented) + threeDimensionalAggregation: { + key: AllowedBucketKeyTypes; + groups: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; + }[]; + // (undocumented) + timestamp: string; + // (undocumented) + twoDimensionalAggregation: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; +} + // @public (undocumented) export type DefaultToFalse = false extends B ? false : undefined extends B ? false : true; @@ -516,6 +627,19 @@ export interface PropertyValueWireToClient { timestamp: string; } +// Warning: (ae-forgotten-export) The symbol "PartialByNotStrict" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "NotOptionalParams_2" needs to be exported by the entry point index.d.ts +// Warning: (ae-forgotten-export) The symbol "OptionalQueryParams" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type QueryParameterType>> = NOOP, OptionalQueryParams>>; + +// @public (undocumented) +export type QueryReturnType> = T extends ObjectQueryDataType ? OsdkBase : T extends ObjectSetQueryDataType ? ObjectSet : T["type"] extends keyof DataValueWireToClient ? DataValueWireToClient[T["type"]] : never; + +// @public (undocumented) +export type QuerySignatureFromDef> = keyof T["parameters"] extends never ? () => Promise> : (params: QueryParameterType) => Promise>; + // Warning: (ae-internal-missing-underscore) The name "RespectNullability" should be prefixed with an underscore because the declaration is marked as @internal // // @internal diff --git a/packages/client.api/src/index.ts b/packages/client.api/src/index.ts index ef5385120..fd7106a64 100644 --- a/packages/client.api/src/index.ts +++ b/packages/client.api/src/index.ts @@ -63,6 +63,12 @@ export type { GroupByClause, GroupByRange, } from "./groupby/GroupByClause.js"; +export type { + AllowedBucketKeyTypes, + AllowedBucketTypes, + DataValueClientToWire, + DataValueWireToClient, +} from "./mapping/DataValueMapping.js"; export type { PropertyValueClientToWire, PropertyValueWireToClient, @@ -108,6 +114,11 @@ export type { } from "./OsdkObjectFrom.js"; export type { OsdkObjectPrimaryKeyType } from "./OsdkObjectPrimaryKeyType.js"; export type { PageResult } from "./PageResult.js"; +export type { + QueryParameterType, + QueryReturnType, + QuerySignatureFromDef, +} from "./queries/Queries.js"; export type { LinkedType, LinkNames } from "./util/LinkUtils.js"; export type { NOOP } from "./util/NOOP.js"; diff --git a/packages/client.api/src/mapping/DataValueMapping.ts b/packages/client.api/src/mapping/DataValueMapping.ts index 1cf82fbcb..42f47bb63 100644 --- a/packages/client.api/src/mapping/DataValueMapping.ts +++ b/packages/client.api/src/mapping/DataValueMapping.ts @@ -34,6 +34,16 @@ export interface DataValueWireToClient { short: number; string: string; timestamp: string; + twoDimensionalAggregation: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; + threeDimensionalAggregation: { + key: AllowedBucketKeyTypes; + groups: { key: AllowedBucketKeyTypes; value: AllowedBucketTypes }[]; + }[]; + struct: Record; + set: Set; } /** @@ -54,4 +64,22 @@ export interface DataValueClientToWire { short: number; string: string; timestamp: string; + set: Set; + twoDimensionalAggregation: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[]; + threeDimensionalAggregation: { + key: AllowedBucketKeyTypes; + groups: { key: AllowedBucketKeyTypes; value: AllowedBucketTypes }[]; + }[]; + struct: Record; } + +export type AllowedBucketTypes = string | number | boolean; +export type AllowedBucketKeyTypes = + | AllowedBucketTypes + | { + startValue: AllowedBucketTypes; + endValue: AllowedBucketTypes; + }; diff --git a/packages/client.api/src/queries/Queries.ts b/packages/client.api/src/queries/Queries.ts new file mode 100644 index 000000000..66e630b5a --- /dev/null +++ b/packages/client.api/src/queries/Queries.ts @@ -0,0 +1,81 @@ +/* + * 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 { + ObjectQueryDataType, + ObjectSetQueryDataType, + OntologyDefinition, + QueryDataTypeDefinition, + QueryDefinition, +} from "@osdk/api"; +import type { + BaseObjectSet, + DataValueClientToWire, + DataValueWireToClient, + NOOP, + ObjectSet, + OsdkBase, + OsdkObjectPrimaryKeyType, +} from "../index.js"; +import type { PartialByNotStrict } from "../util/PartialBy.js"; + +export type Queries> = { + [K in keyof O["queries"]]: QuerySignatureFromDef; +}; + +export type QuerySignatureFromDef> = + keyof T["parameters"] extends never + ? () => Promise> + : ( + params: QueryParameterType, + ) => Promise>; + +export type QueryParameterType< + T extends Record>, +> = NOOP, OptionalQueryParams>>; + +export type QueryReturnType> = + T extends ObjectQueryDataType ? OsdkBase + : T extends ObjectSetQueryDataType + ? ObjectSet + : T["type"] extends keyof DataValueWireToClient + ? DataValueWireToClient[T["type"]] + : never; + +type OptionalQueryParams< + T extends Record>, +> = { + [K in keyof T]: T[K] extends { nullable: true } ? never : K; +}[keyof T]; + +type NotOptionalParams< + T extends Record>, +> = { + [K in keyof T]: MaybeArrayType; +}; + +type MaybeArrayType> = + T["multiplicity"] extends true ? Array> + : BaseType; + +type BaseType> = T extends + ObjectQueryDataType + ? OsdkBase | OsdkObjectPrimaryKeyType + : T extends ObjectSetQueryDataType + ? BaseObjectSet + : T["type"] extends keyof DataValueClientToWire + ? DataValueClientToWire[T["type"]] + : never; diff --git a/packages/client/src/Client.ts b/packages/client/src/Client.ts index eed646a88..7ec520924 100644 --- a/packages/client/src/Client.ts +++ b/packages/client/src/Client.ts @@ -17,9 +17,14 @@ import type { ActionDefinition, ObjectTypeDefinition, + QueryDefinition, VersionBound, } from "@osdk/api"; -import type { ActionSignatureFromDef, ObjectSet } from "@osdk/client.api"; +import type { + ActionSignatureFromDef, + ObjectSet, + QuerySignatureFromDef, +} from "@osdk/client.api"; import type { SharedClient } from "@osdk/shared.client"; import type { MinimalClient } from "./MinimalClientContext.js"; import type { SatisfiesSemver } from "./SatisfiesSemver.js"; @@ -41,6 +46,10 @@ export interface Client extends SharedClient { >( o: CheckVersionBound, ): ActionSignatureFromDef; + + >( + o: CheckVersionBound, + ): QuerySignatureFromDef; } // BEGIN: THIS IS GENERATED CODE. DO NOT EDIT. diff --git a/packages/client/src/createClient.ts b/packages/client/src/createClient.ts index 34bc7775a..71e248a57 100644 --- a/packages/client/src/createClient.ts +++ b/packages/client/src/createClient.ts @@ -19,11 +19,13 @@ import type { InterfaceDefinition, ObjectOrInterfaceDefinition, ObjectTypeDefinition, + QueryDefinition, } from "@osdk/api"; import type { ActionSignatureFromDef, MinimalObjectSet, ObjectSet, + QuerySignatureFromDef, } from "@osdk/client.api"; import { symbolClientContext } from "@osdk/shared.client"; import type { Logger } from "pino"; @@ -33,6 +35,7 @@ import { createMinimalClient } from "./createMinimalClient.js"; import type { MinimalClient } from "./MinimalClientContext.js"; import { createObjectSet } from "./objectSet/createObjectSet.js"; import type { ObjectSetFactory } from "./objectSet/ObjectSetFactory.js"; +import { createQueryInvoker } from "./queries/createQueryInvoker.js"; class ActionInvoker> implements ActionSignatureFromDef @@ -71,10 +74,12 @@ export function createClientInternal( function clientFn< T extends | ObjectOrInterfaceDefinition - | ActionDefinition, + | ActionDefinition + | QueryDefinition, >(o: T): T extends ObjectTypeDefinition ? ObjectSet : T extends InterfaceDefinition ? MinimalObjectSet : T extends ActionDefinition ? ActionSignatureFromDef + : T extends QueryDefinition ? QuerySignatureFromDef : never { if (o.type === "object" || o.type === "interface") { @@ -89,6 +94,10 @@ export function createClientInternal( // first `as` to the action definition for our "real" typecheck ? ActionSignatureFromDef : never) as any; // then as any for dealing with the conditional return value + } else if (o.type === "query") { + return createQueryInvoker(clientCtx, o) as QuerySignatureFromDef< + any + > as any; } else { throw new Error("not implemented"); } diff --git a/packages/client/src/queries/applyQuery.ts b/packages/client/src/queries/applyQuery.ts new file mode 100644 index 000000000..ce1de4882 --- /dev/null +++ b/packages/client/src/queries/applyQuery.ts @@ -0,0 +1,337 @@ +/* + * 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 { + InterfaceDefinition, + ObjectOrInterfaceDefinition, + ObjectTypeDefinition, + QueryDataTypeDefinition, + QueryDefinition, + QueryParameterDefinition, +} from "@osdk/api"; +import type { + AllowedBucketKeyTypes, + AllowedBucketTypes, + OsdkBase, + OsdkObjectPrimaryKeyType, + QueryParameterType, + QueryReturnType, +} from "@osdk/client.api"; +import type { + DataValue, + ObjectSet as WireObjectSet, +} from "@osdk/internal.foundry"; +import { OntologiesV2 } from "@osdk/internal.foundry"; +import { createAttachmentFromRid } from "../createAttachmentFromRid.js"; +import type { MinimalClient } from "../MinimalClientContext.js"; +import { createObjectSet } from "../objectSet/createObjectSet.js"; +import { addUserAgent } from "../util/addUserAgent.js"; +import { toDataValue } from "../util/toDataValue.js"; +import { toDataValueQueries } from "../util/toDataValueQueries.js"; + +export async function applyQuery< + QD extends QueryDefinition, + P extends QueryParameterType, +>( + client: MinimalClient, + query: QD, + params?: P, +): Promise< + QueryReturnType +> { + const response = await OntologiesV2.QueryTypes.executeQueryV2( + addUserAgent(client, query), + await client.ontologyRid, + query.apiName, + { + parameters: params + ? await remapQueryParams( + params as { [parameterId: string]: any }, + client, + query.parameters, + ) + : {}, + }, + ); + const objectOutputDefs = await getRequiredDefinitions(query.output, client); + const remappedResponse = await remapQueryResponse( + client, + query.output, + response.value, + objectOutputDefs, + ); + return remappedResponse as QueryReturnType; +} + +async function remapQueryParams( + params: { [parameterId: string]: any }, + client: MinimalClient, + paramTypes: Record>, +): Promise<{ [parameterId: string]: any }> { + const parameterMap: { [parameterName: string]: unknown } = {}; + for (const [key, value] of Object.entries(params)) { + parameterMap[key] = await toDataValueQueries( + value, + client, + paramTypes[key], + ); + } + return parameterMap; +} + +async function remapQueryResponse< + K extends string, + Q extends ObjectTypeDefinition, + T extends QueryDataTypeDefinition, +>( + client: MinimalClient, + responseDataType: T, + responseValue: DataValue, + definitions: Map, +): Promise> { + // handle null responses + if (responseValue == null) { + if (responseDataType.nullable) { + return undefined as unknown as QueryReturnType; + } else { + throw new Error("Got null response when nullable was not allowed"); + } + } + + if ( + responseDataType.multiplicity != null + && responseDataType.multiplicity !== false + ) { + const withoutMultiplicity = { ...responseDataType, multiplicity: false }; + for (let i = 0; i < responseValue.length; i++) { + responseValue[i] = await remapQueryResponse( + responseValue[i], + withoutMultiplicity, + client, + definitions, + ); + } + return responseValue as QueryReturnType; + } + + switch (responseDataType.type) { + case "union": { + throw new Error("Union return types are not yet supported"); + } + + case "set": { + for (let i = 0; i < responseValue.length; i++) { + responseValue[i] = await remapQueryResponse( + responseValue[i], + responseDataType.set, + client, + definitions, + ); + } + + return responseValue as QueryReturnType; + } + + case "attachment": { + return createAttachmentFromRid(client, responseValue) as QueryReturnType< + typeof responseDataType + >; + } + case "object": { + const def = definitions.get(responseDataType.object); + if (!def) { + throw new Error( + `Missing definition for ${responseDataType.object}`, + ); + } + return createQueryObjectResponse( + responseValue, + def, + ) as QueryReturnType< + typeof responseDataType + >; + } + + case "objectSet": { + const def = definitions.get(responseDataType.objectSet); + if (!def) { + throw new Error( + `Missing definition for ${responseDataType.objectSet}`, + ); + } + if (typeof responseValue === "string") { + return createObjectSet(def, client, { + type: "intersect", + objectSets: [ + { type: "base", objectType: responseDataType.objectSet }, + { type: "reference", reference: responseValue }, + ], + }) as QueryReturnType; + } + + return createObjectSet( + def, + client, + responseValue, + ) as QueryReturnType< + typeof responseDataType + >; + } + case "struct": { + // figure out what keys need to be fixed up + for (const [key, subtype] of Object.entries(responseDataType.struct)) { + if (requiresConversion(subtype)) { + responseValue[key] = await remapQueryResponse( + responseValue[key], + subtype, + client, + definitions, + ); + } + } + + return responseValue as QueryReturnType; + } + case "twoDimensionalAggregation": { + const result: { + key: AllowedBucketKeyTypes; + value: AllowedBucketTypes; + }[] = []; + for (const { key, value } of responseValue.groups) { + result.push({ key, value }); + } + return result as QueryReturnType; + } + + case "threeDimensionalAggregation": { + const result: { + key: AllowedBucketKeyTypes; + groups: { key: AllowedBucketKeyTypes; value: AllowedBucketTypes }[]; + }[] = []; + for (const { key, groups } of responseValue.groups) { + const subresult: { key: any; value: any }[] = []; + for (const { key: subkey, value } of groups) { + subresult.push({ key: subkey, value }); + } + result.push({ key, groups: subresult }); + } + return result as QueryReturnType; + } + } + + return responseValue as QueryReturnType; +} + +async function getRequiredDefinitions( + dataType: QueryDataTypeDefinition, + client: MinimalClient, +): Promise> { + const result = new Map(); + switch (dataType.type) { + case "objectSet": { + const objectDef = await client.ontologyProvider.getObjectDefinition( + dataType.objectSet, + ); + result.set(dataType.objectSet, objectDef); + break; + } + case "object": { + const objectDef = await client.ontologyProvider.getObjectDefinition( + dataType.object, + ); + result.set(dataType.object, objectDef); + break; + } + + case "set": { + return getRequiredDefinitions(dataType.set, client); + } + + case "struct": { + for (const value of Object.values(dataType.struct)) { + for ( + const [type, objectDef] of await getRequiredDefinitions(value, client) + ) { + result.set(type, objectDef); + } + } + break; + } + case "attachment": + case "boolean": + case "date": + case "double": + case "float": + case "integer": + case "long": + case "object": + case "string": + case "threeDimensionalAggregation": + case "timestamp": + case "twoDimensionalAggregation": + case "union": + break; + } + + return result; +} +function requiresConversion(dataType: QueryDataTypeDefinition) { + switch (dataType.type) { + case "boolean": + case "date": + case "double": + case "float": + case "integer": + case "long": + case "object": // JSON encoded primary key + case "string": + case "timestamp": + return false; + + case "union": + return true; + + case "struct": + return Object.values(dataType.struct).some(requiresConversion); + + case "set": + return requiresConversion(dataType.set); + + case "attachment": + case "objectSet": + case "twoDimensionalAggregation": + case "threeDimensionalAggregation": + return true; + + default: + return false; + } +} + +export function createQueryObjectResponse< + Q extends ObjectTypeDefinition | InterfaceDefinition, +>( + primaryKey: Q extends ObjectTypeDefinition ? OsdkObjectPrimaryKeyType + : (string | number), + objectDef: Q, +): OsdkBase { + return { + $apiName: objectDef.apiName, + $title: undefined, + $objectType: objectDef.apiName, + $primaryKey: primaryKey, + }; +} diff --git a/packages/client/src/queries/createQueryInvoker.ts b/packages/client/src/queries/createQueryInvoker.ts new file mode 100644 index 000000000..a0267b0be --- /dev/null +++ b/packages/client/src/queries/createQueryInvoker.ts @@ -0,0 +1,29 @@ +/* + * 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 { OntologyDefinition, QueryDefinition } from "@osdk/api"; +import type { QuerySignatureFromDef } from "@osdk/client.api"; +import type { MinimalClient } from "../MinimalClientContext.js"; +import { applyQuery } from "./applyQuery.js"; + +export function createQueryInvoker>( + client: MinimalClient, + query: Q, +): QuerySignatureFromDef { + return function(...args: any[]) { + return applyQuery(client, query, ...args); + }; +} diff --git a/packages/client/src/queries/queries.test.ts b/packages/client/src/queries/queries.test.ts new file mode 100644 index 000000000..97ba1afb7 --- /dev/null +++ b/packages/client/src/queries/queries.test.ts @@ -0,0 +1,205 @@ +/* + * 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. + */ + +import type { ObjectSet, QuerySignatureFromDef } from "@osdk/client.api"; +import type { Employee } from "@osdk/client.test.ontology"; +import { + acceptsThreeDimensionalAggregationFunction, + acceptsTwoDimensionalAggregationFunction, + addOne, + incrementPersonAge, + Ontology as MockOntology, + queryAcceptsObject, + queryAcceptsObjectSets, + returnsDate, + returnsTimestamp, + threeDimensionalAggregationFunction, + twoDimensionalAggregationFunction, +} from "@osdk/client.test.ontology"; +import { apiServer } from "@osdk/shared.test"; +import { + afterAll, + beforeAll, + describe, + expect, + expectTypeOf, + it, +} from "vitest"; +import type { Client } from "../Client.js"; +import { createClient } from "../createClient.js"; + +describe("queries", () => { + let client: Client; + + beforeAll(async () => { + apiServer.listen(); + client = createClient( + "https://stack.palantir.com", + MockOntology.metadata.ontologyRid, + async () => "myAccessToken", + ); + }); + + afterAll(() => { + apiServer.close(); + }); + + it("simple query works", async () => { + const result = await client(addOne)({ n: 2 }); + expect(result).toBe(3); + }); + + it("accepts objects", async () => { + const employee = await client(MockOntology.objects.Employee).fetchOne( + 50030, + ); + const result = await client(queryAcceptsObject)({ object: employee }); + expect(result).toEqual({ + $apiName: "Employee", + $objectType: "Employee", + $primaryKey: 50031, + }); + + // Should also accept primary keys + const result2 = await client(queryAcceptsObject)({ object: 50030 }); + expect(result2).toEqual({ + $apiName: "Employee", + $objectType: "Employee", + $primaryKey: 50031, + }); + }); + + it("accepts objectSets", async () => { + const employeeObjectSet = client(MockOntology.objects.Employee); + const result = await client(queryAcceptsObjectSets)({ + objectSet: employeeObjectSet, + }); + + expectTypeOf().toMatchTypeOf>(); + }); + + it("no params work", async () => { + const resultWithTimestamp = await client(returnsTimestamp)(); + expect(resultWithTimestamp).toBe("2019-01-01T00:00:00.000Z"); + + const resultWithDate = await client(returnsDate)(); + expect(resultWithDate).toBe("2019-01-01"); + }); + + it("returns and accepts structs property", async () => { + const result = await client(incrementPersonAge)({ + person: { firstName: "John", lastName: "Doe", age: 42 }, + }); + expect(result).toEqual({ + firstName: "John", + lastName: "Doe", + age: 43, + }); + }); + + it("two dimensional aggs response works", async () => { + const result = await client(twoDimensionalAggregationFunction)(); + expect(result).toEqual([{ key: "Q-AFN", value: 1 }, { + key: "Q-AFO", + value: 2, + }]); + }); + + it("two dimensional aggs request/response works", async () => { + const result = await client(acceptsTwoDimensionalAggregationFunction)({ + aggFunction: [ + { + key: "testKey1", + value: 1, + }, + { + key: "testKey2", + value: 2, + }, + ], + }); + expect(result).toEqual([{ key: "responseKey1", value: 3 }, { + key: "responseKey2", + value: 4, + }]); + }); + + it("three dimensional aggs response works", async () => { + const result = await client(threeDimensionalAggregationFunction)(); + expect(result).toEqual([{ + key: "Q-AFN", + groups: [{ + key: { + startValue: "2010-10-01T00:00:00Z", + endValue: "2010-10-02T00:00:00Z", + }, + value: 65.0, + }], + }, { key: "Q-AFO", groups: [] }]); + }); + + it("throws when response is null and response is non-nullable", async () => { + try { + const result = await client(addOne)({ n: 3 }); + expect.fail("Should not reach here"); + } catch (e) { + expect((e as Error).message).toMatch( + `Got null response when nullable was not allowed`, + ); + } + }); + + it("three dimensional aggs request/response works", async () => { + const result = await client(acceptsThreeDimensionalAggregationFunction)({ + aggFunction: [ + { + key: "testKey1", + groups: [ + { + key: { + startValue: "2010-10-01T00:00:00Z", + endValue: "2010-10-02T00:00:00Z", + }, + value: 65.0, + }, + ], + }, + { + key: "testKey2", + groups: [], + }, + ], + }); + expect(result).toEqual([ + { + key: "Q-AFN", + groups: [ + { + key: { + startValue: "2010-10-01T00:00:00Z", + endValue: "2010-10-02T00:00:00Z", + }, + value: 65.0, + }, + ], + }, + { + key: "Q-AFO", + groups: [], + }, + ]); + }); +}); diff --git a/packages/client/src/util/isOsdkBaseObject.ts b/packages/client/src/util/isOsdkBaseObject.ts new file mode 100644 index 000000000..8112c42c4 --- /dev/null +++ b/packages/client/src/util/isOsdkBaseObject.ts @@ -0,0 +1,22 @@ +/* + * 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 { OsdkBase } from "@osdk/client.api"; + +export function isOsdkBaseObject(o: any): o is OsdkBase { + return o && typeof o === "object" && typeof o.$apiName === "string" + && o.$primaryKey != null; +} diff --git a/packages/client/src/util/toDataValueQueries.ts b/packages/client/src/util/toDataValueQueries.ts new file mode 100644 index 000000000..1f1ad8d5b --- /dev/null +++ b/packages/client/src/util/toDataValueQueries.ts @@ -0,0 +1,131 @@ +/* + * 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 { QueryDataTypeDefinition } from "@osdk/api"; +import { type DataValue, Ontologies } from "@osdk/internal.foundry"; +import type { MinimalClient } from "../MinimalClientContext.js"; +import { isAttachmentUpload } from "../object/AttachmentUpload.js"; +import { getWireObjectSet, isObjectSet } from "../objectSet/createObjectSet.js"; +import { isOsdkBaseObject } from "./isOsdkBaseObject.js"; +import { isWireObjectSet } from "./WireObjectSet.js"; + +/** + * Marshall user-facing data into the wire DataValue type + * + * @see DataValue for the expected payloads + */ +export async function toDataValueQueries( + value: unknown, + client: MinimalClient, + desiredType: QueryDataTypeDefinition, +): Promise { + if (value == null) { + return value; + } + + if (Array.isArray(value) && desiredType.multiplicity) { + const promiseArray = Array.from( + value, + async (innerValue) => + await toDataValueQueries(innerValue, client, desiredType), + ); + return Promise.all(promiseArray); + } + + switch (desiredType.type) { + case "attachment": { + if (isAttachmentUpload(value)) { + const attachment = await Ontologies.Attachments.uploadAttachment( + client, + value, + { + filename: value.name, + }, + { + "Content-Length": value.size.toString(), + "Content-Type": value.type, + }, + ); + return attachment.rid; + } + + // If it's not an upload, it's just an attachment rid string which we can pass through + return value; + } + case "twoDimensionalAggregation": { + return { + groups: value, + }; + } + case "threeDimensionalAggregation": { + return { + groups: value, + }; + } + + case "set": { + if (value instanceof Set) { + const promiseArray = Array.from( + value, + async (innerValue) => + await toDataValueQueries(innerValue, client, desiredType["set"]), + ); + return Promise.all(promiseArray); + } + break; + } + case "object": { + if (isOsdkBaseObject(value)) { + return value.$primaryKey; + } + break; + } + case "objectSet": { + // object set (the rid as a string (passes through the last return), or the ObjectSet definition directly) + if (isWireObjectSet(value)) { + return value; + } + if (isObjectSet(value)) { + return getWireObjectSet(value); + } + break; + } + + case "struct": { + if (typeof value === "object") { + const structMap: { [key: string]: unknown } = {}; + for (const [key, structValue] of Object.entries(value)) { + structMap[key] = await toDataValueQueries( + structValue, + client, + desiredType["struct"][key], + ); + } + return structMap; + } + } + case "boolean": + case "date": + case "double": + case "float": + case "integer": + case "long": + case "string": + case "timestamp": + return value; + } + return value; +} diff --git a/packages/generator/src/shared/wireQueryDataTypeToQueryDataTypeDefinition.ts b/packages/generator/src/shared/wireQueryDataTypeToQueryDataTypeDefinition.ts index 39f87333d..e5455434e 100644 --- a/packages/generator/src/shared/wireQueryDataTypeToQueryDataTypeDefinition.ts +++ b/packages/generator/src/shared/wireQueryDataTypeToQueryDataTypeDefinition.ts @@ -28,10 +28,13 @@ import type { TwoDimensionalAggregation, } from "@osdk/gateway/types"; import { isNullableQueryDataType } from "./isNullableQueryDataType.js"; +import { getObjectDefIdentifier } from "./wireObjectTypeV2ToSdkObjectConst.js"; -export function wireQueryDataTypeToQueryDataTypeDefinition( +export function wireQueryDataTypeToQueryDataTypeDefinition< + K extends string, +>( input: QueryDataType, -): QueryDataTypeDefinition { +): QueryDataTypeDefinition { switch (input.type) { case "double": case "float": @@ -114,12 +117,14 @@ export function wireQueryDataTypeToQueryDataTypeDefinition( return { type: "twoDimensionalAggregation", twoDimensionalAggregation: get2DQueryAggregationProps(input), + nullable: false, }; case "threeDimensionalAggregation": return { type: "threeDimensionalAggregation", threeDimensionalAggregation: get3DQueryAggregationProps(input), + nullable: false, }; case "null": diff --git a/packages/generator/src/shared/wireQueryTypeV2ToSdkQueryDefinition.ts b/packages/generator/src/shared/wireQueryTypeV2ToSdkQueryDefinition.ts index 150ac30ba..468e1ade1 100644 --- a/packages/generator/src/shared/wireQueryTypeV2ToSdkQueryDefinition.ts +++ b/packages/generator/src/shared/wireQueryTypeV2ToSdkQueryDefinition.ts @@ -16,7 +16,9 @@ import type { QueryDefinition, QueryParameterDefinition } from "@osdk/api"; import type { QueryParameterV2, QueryTypeV2 } from "@osdk/gateway/types"; -import { wireQueryDataTypeToQueryDataTypeDefinition } from "./wireQueryDataTypeToQueryDataTypeDefinition.js"; +import { + wireQueryDataTypeToQueryDataTypeDefinition, +} from "./wireQueryDataTypeToQueryDataTypeDefinition.js"; export function wireQueryTypeV2ToSdkQueryDefinition( input: QueryTypeV2, @@ -36,7 +38,19 @@ export function wireQueryTypeV2ToSdkQueryDefinition( }; } -function wireQueryParameterV2ToQueryParameterDefinition( +export function wireQueryTypeV2ToSdkQueryDefinitionNoParams( + input: QueryTypeV2, +) { + return { + type: "query", + apiName: input.apiName, + description: input.description, + displayName: input.displayName, + version: input.version, + }; +} + +export function wireQueryParameterV2ToQueryParameterDefinition( parameter: QueryParameterV2, ): QueryParameterDefinition { return { diff --git a/packages/generator/src/v1.1/generatePerQueryDataFiles.ts b/packages/generator/src/v1.1/generatePerQueryDataFiles.ts index b928fde94..b94185458 100644 --- a/packages/generator/src/v1.1/generatePerQueryDataFiles.ts +++ b/packages/generator/src/v1.1/generatePerQueryDataFiles.ts @@ -17,7 +17,15 @@ import type { QueryDataType, QueryTypeV2 } from "@osdk/gateway/types"; import path from "node:path"; import type { MinimalFs } from "../MinimalFs.js"; -import { wireQueryTypeV2ToSdkQueryDefinition } from "../shared/wireQueryTypeV2ToSdkQueryDefinition.js"; +import { getObjectDefIdentifier } from "../shared/wireObjectTypeV2ToSdkObjectConst.js"; +import { wireQueryDataTypeToQueryDataTypeDefinition } from "../shared/wireQueryDataTypeToQueryDataTypeDefinition.js"; +import { + wireQueryParameterV2ToQueryParameterDefinition, + wireQueryTypeV2ToSdkQueryDefinition, + wireQueryTypeV2ToSdkQueryDefinitionNoParams, +} from "../shared/wireQueryTypeV2ToSdkQueryDefinition.js"; +import { deleteUndefineds } from "../util/deleteUndefineds.js"; +import { stringify } from "../util/stringify.js"; import { formatTs } from "../util/test/formatTs.js"; import type { WireOntologyDefinition } from "../WireOntologyDefinition.js"; @@ -26,24 +34,76 @@ export async function generatePerQueryDataFiles( fs: MinimalFs, outDir: string, importExt: string = "", + v2: boolean = false, ) { await fs.mkdir(outDir, { recursive: true }); await Promise.all( Object.values(ontology.queryTypes).map(async query => { const objectTypes = getObjectTypesFromQuery(query); - await fs.writeFile( - path.join(outDir, `${query.apiName}.ts`), - await formatTs(` - import { QueryDefinition } from "@osdk/api"; - - export const ${query.apiName} = ${ - JSON.stringify(wireQueryTypeV2ToSdkQueryDefinition(query)) - } satisfies QueryDefinition<"${query.apiName}", ${ - objectTypes.length > 0 - ? objectTypes.map(apiName => `"${apiName}"`).join("|") - : "never" - }>;`), - ); + const importObjects = objectTypes.length > 0 + ? `import {${ + [...objectTypes].join(",") + }} from "../objects${importExt}";` + : ""; + if (v2) { + await fs.writeFile( + path.join(outDir, `${query.apiName}.ts`), + await formatTs(` + import { QueryDefinition } from "@osdk/api"; + ${importObjects} + export const ${query.apiName} = { + ${ + stringify( + deleteUndefineds( + wireQueryTypeV2ToSdkQueryDefinitionNoParams(query), + ), + ) + }, + parameters: {${ + Object.entries(query.parameters).map(( + [name, parameter], + ) => { + return `${name} : {${ + stringify(deleteUndefineds( + wireQueryParameterV2ToQueryParameterDefinition(parameter), + )) + }, + ${ + parameter.dataType.type === "object" + || parameter.dataType.type === "objectSet" + ? getOsdkTargetTypeIfPresent( + parameter.dataType.objectTypeApiName!, + v2, + ) + : `` + }}`; + }) + }}, + output: {${ + stringify( + deleteUndefineds( + wireQueryDataTypeToQueryDataTypeDefinition(query.output), + ), + ) + }, + ${ + query.output.type === "object" || query.output.type === "objectSet" + ? getOsdkTargetTypeIfPresent(query.output.objectTypeApiName!, v2) + : `` + }} + } ${getQueryDefSatisfies(query.apiName, objectTypes)}`), + ); + } else { + await fs.writeFile( + path.join(outDir, `${query.apiName}.ts`), + await formatTs(` + import { QueryDefinition } from "@osdk/api"; + + export const ${query.apiName} = ${ + JSON.stringify(wireQueryTypeV2ToSdkQueryDefinition(query)) + } ${getQueryDefSatisfies(query.apiName, objectTypes)}`), + ); + } }), ); @@ -56,6 +116,7 @@ export async function generatePerQueryDataFiles( ) .join("\n") } + ${Object.keys(ontology.queryTypes).length === 0 ? "export {};" : ""} `), ); } @@ -126,3 +187,25 @@ function getObjectTypesFromDataType( ); } } + +function getQueryDefSatisfies(apiName: string, objectTypes: string[]): string { + return `satisfies QueryDefinition<"${apiName}", ${ + objectTypes.length > 0 + ? objectTypes.map(apiNameObj => `"${apiNameObj}"`).join("|") + : "never" + }>;`; +} + +function getOsdkTargetTypeIfPresent( + objectTypeApiName: string, + v2: boolean, +): string { + return ` + __OsdkTargetType: ${ + getObjectDefIdentifier( + objectTypeApiName, + v2, + ) + } + `; +} diff --git a/packages/generator/src/v2.0/generateClientSdkVersionTwoPointZero.ts b/packages/generator/src/v2.0/generateClientSdkVersionTwoPointZero.ts index f607edf79..bc445f0dd 100644 --- a/packages/generator/src/v2.0/generateClientSdkVersionTwoPointZero.ts +++ b/packages/generator/src/v2.0/generateClientSdkVersionTwoPointZero.ts @@ -24,6 +24,7 @@ import { } from "../shared/wireObjectTypeV2ToSdkObjectConst.js"; import { formatTs } from "../util/test/formatTs.js"; import { verifyOutdir } from "../util/verifyOutdir.js"; +import { generatePerQueryDataFiles } from "../v1.1/generatePerQueryDataFiles.js"; import type { WireOntologyDefinition } from "../WireOntologyDefinition.js"; import { generateOntologyMetadataFile } from "./generateMetadata.js"; @@ -62,6 +63,7 @@ export async function generateClientSdkVersionTwoPointZero( export * from "./ontology/actions/index${importExt}"; export * from "./ontology/objects${importExt}"; export * from "./ontology/interfaces${importExt}"; + export * from "./ontology/queries/index${importExt}"; `, ), ); @@ -76,6 +78,7 @@ export async function generateClientSdkVersionTwoPointZero( import * as Actions from "./ontology/actions/index${importExt}"; import * as Objects from "./ontology/objects${importExt}"; import * as Interfaces from "./ontology/interfaces${importExt}"; + import * as Queries from "./ontology/queries/index${importExt}"; import { OntologyMetadata } from "./OntologyMetadata${importExt}"; export interface Ontology extends OntologyDefinition<${ @@ -97,7 +100,11 @@ export async function generateClientSdkVersionTwoPointZero( } }, queries: { - // TODO + ${ + queryNames.map((queryName) => { + return `${queryName}: typeof Queries.${queryName}`; + }).join(",\n") + } }, interfaces: { ${ @@ -126,7 +133,11 @@ export async function generateClientSdkVersionTwoPointZero( } }, queries: { - // TODO + ${ + queryNames.map((queryName) => { + return `${queryName}: Queries.${queryName}`; + }).join(",\n") + } }, interfaces: { ${ @@ -188,6 +199,16 @@ export async function generateClientSdkVersionTwoPointZero( ${Object.keys(ontology.objectTypes).length === 0 ? "export {};" : ""} `), ); + + const queriesDir = path.join(outDir, "ontology", "queries"); + await fs.mkdir(queriesDir, { recursive: true }); + await generatePerQueryDataFiles( + sanitizedOntology, + fs, + queriesDir, + importExt, + true, + ); } function stringUnionFrom(values: ReadonlyArray) { diff --git a/packages/legacy-client/src/client/queries.ts b/packages/legacy-client/src/client/queries.ts index 09ed71a32..6cb197f2a 100644 --- a/packages/legacy-client/src/client/queries.ts +++ b/packages/legacy-client/src/client/queries.ts @@ -103,7 +103,7 @@ export type QueryNamesFrom> = export type QueryDataType< O extends OntologyDefinition, - D extends QueryDataTypeDefinition, + D extends QueryDataTypeDefinition, T_ReturnValue extends boolean, > = D["multiplicity"] extends true ? Array> @@ -130,12 +130,12 @@ export type QueryDataTypeBase< ? (R extends true ? X extends "long" ? string : ValidLegacyBaseQueryDataTypes[X] : ValidLegacyBaseQueryDataTypes[X]) - : T extends ObjectQueryDataType + : T extends ObjectQueryDataType ? R extends true ? OsdkLegacyObjectFrom : | OsdkLegacyObjectFrom | OsdkLegacyObjectFrom["__primaryKey"] - : T extends ObjectSetQueryDataType + : T extends ObjectSetQueryDataType ? ObjectSet> : T extends SetQueryDataType ? Set> : T extends TwoDimensionalAggregationDataType ? TwoDimensionalAggregation< diff --git a/packages/shared.test/src/errors.ts b/packages/shared.test/src/errors.ts index a1a93a715..6660a828e 100644 --- a/packages/shared.test/src/errors.ts +++ b/packages/shared.test/src/errors.ts @@ -107,6 +107,18 @@ export const ApplyActionFailedError: errors.ApplyActionFailed = { parameters: {}, }; +export const ExecuteQueryFailedError: errors.QueryEncounteredUserFacingError = { + errorCode: "CONFLICT", + errorName: "QueryEncounteredUserFacingError", + errorInstanceId, + parameters: { + functionRid: + "ri.function-registry.main.function.9b55870a-63c7-4d48-8f06-9627c0805968", + functionVersion: "0.11.0", + message: "test failed", + }, +}; + export const InvalidContentTypeError: errors.InvalidContentType = { errorCode: "INVALID_ARGUMENT", errorName: "InvalidContentType", diff --git a/packages/shared.test/src/handlers/loadObjectsEndpoints.ts b/packages/shared.test/src/handlers/loadObjectsEndpoints.ts index eeb75b27b..5084c07e8 100644 --- a/packages/shared.test/src/handlers/loadObjectsEndpoints.ts +++ b/packages/shared.test/src/handlers/loadObjectsEndpoints.ts @@ -493,7 +493,8 @@ export const loadObjectsEndpoints: Array = [ const queryResponse = queryResponses[JSON.stringify(parsedBody)]; if ( req.params.ontologyApiName === defaultOntology.apiName - && queryResponse + || req.params.ontologyApiName === defaultOntology.rid + && queryResponse ) { return queryResponse; } diff --git a/packages/shared.test/src/stubs/queries.ts b/packages/shared.test/src/stubs/queries.ts index ad2c03145..e0be42930 100644 --- a/packages/shared.test/src/stubs/queries.ts +++ b/packages/shared.test/src/stubs/queries.ts @@ -18,9 +18,13 @@ import type { ExecuteQueryRequest, ExecuteQueryResponse, } from "@osdk/gateway/types"; -import { employee1 } from "./objects.js"; +import { employee1, employee2 } from "./objects.js"; import { addOneQueryType, + queryTypeAcceptsObjects, + queryTypeAcceptsObjectSets, + queryTypeAcceptsThreeDimensionalAggregation, + queryTypeAcceptsTwoDimensionalAggregation, queryTypeReturnsDate, queryTypeReturnsObject, queryTypeReturnsStruct, @@ -35,6 +39,12 @@ export const addOneQueryRequest: ExecuteQueryRequest = { }, }; +export const addOneQueryRequestWithNoResponse: ExecuteQueryRequest = { + parameters: { + n: 3, + }, +}; + export const addOneQueryResponse: ExecuteQueryResponse = { value: 3, }; @@ -69,6 +79,26 @@ export const queryTypeReturnsObjectResponse: ExecuteQueryResponse = { value: employee1.__primaryKey, }; +export const queryTypeAcceptsObjectRequest: ExecuteQueryRequest = { + parameters: { object: employee1.__primaryKey }, +}; + +export const queryTypeAcceptsObjectResponse: ExecuteQueryResponse = { + value: employee2.__primaryKey, +}; + +export const queryTypeAcceptsObjectSetRequest: ExecuteQueryRequest = { + parameters: { + objectSet: { type: "base", objectType: "Employee" }, + }, +}; + +export const queryTypeAcceptsObjectSetResponse: ExecuteQueryResponse = { + value: { + objectSet: { type: "base", objectType: "Employee" }, + }, +}; + export const queryTypeThreeDimensionalAggregationResponse: ExecuteQueryResponse = { value: { @@ -109,6 +139,90 @@ export const queryTypeTwoDimensionalAggregationResponse: ExecuteQueryResponse = }, }; +export const queryTypeAcceptsTwoDimensionalAggregationRequest: + ExecuteQueryRequest = { + parameters: { + aggFunction: { + groups: [ + { + key: "testKey1", + value: 1, + }, + { + key: "testKey2", + value: 2, + }, + ], + }, + }, + }; + +export const queryTypeAcceptsTwoDimensionalAggregationResponse: + ExecuteQueryResponse = { + value: { + groups: [ + { + key: "responseKey1", + value: 3, + }, + { + key: "responseKey2", + value: 4, + }, + ], + }, + }; + +export const queryTypeAcceptsThreeDimensionalAggregationRequest: + ExecuteQueryRequest = { + parameters: { + aggFunction: { + groups: [ + { + key: "testKey1", + groups: [ + { + key: { + startValue: "2010-10-01T00:00:00Z", + endValue: "2010-10-02T00:00:00Z", + }, + value: 65.0, + }, + ], + }, + { + key: "testKey2", + groups: [], + }, + ], + }, + }, + }; + +export const queryTypeAcceptsThreeDimensionalAggregationResponse: + ExecuteQueryResponse = { + value: { + groups: [ + { + key: "Q-AFN", + groups: [ + { + key: { + startValue: "2010-10-01T00:00:00Z", + endValue: "2010-10-02T00:00:00Z", + }, + value: 65.0, + }, + ], + }, + { + key: "Q-AFO", + groups: [], + }, + ], + }, + }; + export const emptyBody: string = JSON.stringify({ parameters: {}, }); @@ -120,6 +234,7 @@ export const queryRequestHandlers: { } = { [addOneQueryType.apiName]: { [JSON.stringify(addOneQueryRequest)]: addOneQueryResponse, + [JSON.stringify(addOneQueryRequestWithNoResponse)]: { value: undefined }, }, [queryTypeReturnsStruct.apiName]: { [JSON.stringify(queryTypeReturnsStructRequest)]: @@ -140,4 +255,20 @@ export const queryRequestHandlers: { [queryTypeThreeDimensionalAggregation.apiName]: { [emptyBody]: queryTypeThreeDimensionalAggregationResponse, }, + [queryTypeAcceptsObjects.apiName]: { + [JSON.stringify(queryTypeAcceptsObjectRequest)]: + queryTypeAcceptsObjectResponse, + }, + [queryTypeAcceptsObjectSets.apiName]: { + [JSON.stringify(queryTypeAcceptsObjectSetRequest)]: + queryTypeAcceptsObjectSetResponse, + }, + [queryTypeAcceptsTwoDimensionalAggregation.apiName]: { + [JSON.stringify(queryTypeAcceptsTwoDimensionalAggregationRequest)]: + queryTypeAcceptsTwoDimensionalAggregationResponse, + }, + [queryTypeAcceptsThreeDimensionalAggregation.apiName]: { + [JSON.stringify(queryTypeAcceptsThreeDimensionalAggregationRequest)]: + queryTypeAcceptsThreeDimensionalAggregationResponse, + }, }; diff --git a/packages/shared.test/src/stubs/queryTypes.ts b/packages/shared.test/src/stubs/queryTypes.ts index be9c62748..c4f480766 100644 --- a/packages/shared.test/src/stubs/queryTypes.ts +++ b/packages/shared.test/src/stubs/queryTypes.ts @@ -190,6 +190,127 @@ export const queryTypeTwoDimensionalAggregation: QueryTypeV2 = { version: "0.11.0", }; +export const queryTypeAcceptsTwoDimensionalAggregation: QueryTypeV2 = { + apiName: "acceptsTwoDimensionalAggregationFunction", + displayName: "acceptsTwoDimensionalAggregation", + parameters: { + aggFunction: { + dataType: { + type: "twoDimensionalAggregation", + keyType: { + type: "string", + }, + valueType: { + type: "double", + }, + }, + }, + }, + output: { + type: "twoDimensionalAggregation", + keyType: { + type: "string", + }, + valueType: { + type: "double", + }, + }, + rid: + "ri.function-registry.main.function.9b55870a-63c7-4d48-8f06-9627c0805968", + version: "0.11.0", +}; + +export const queryTypeAcceptsThreeDimensionalAggregation: QueryTypeV2 = { + apiName: "acceptsThreeDimensionalAggregationFunction", + displayName: "acceptsThreeDimensionalAggregation", + parameters: { + aggFunction: { + dataType: { + type: "threeDimensionalAggregation", + keyType: { + type: "string", + }, + valueType: { + keyType: { + type: "range", + subType: { + type: "timestamp", + }, + }, + valueType: { + type: "double", + }, + }, + }, + }, + }, + output: { + type: "threeDimensionalAggregation", + keyType: { + type: "string", + }, + valueType: { + keyType: { + type: "range", + subType: { + type: "timestamp", + }, + }, + valueType: { + type: "double", + }, + }, + }, + rid: + "ri.function-registry.main.function.9b55870a-63c7-4d48-8f06-9627c0805968", + version: "0.11.0", +}; + +export const queryTypeAcceptsObjects: QueryTypeV2 = { + apiName: "queryAcceptsObject", + description: "description of the query that takes object types", + displayName: "QueryAcceptsObject", + parameters: { + object: { + dataType: { + type: "object", + objectApiName: "Employee", + objectTypeApiName: "Employee", + }, + }, + }, + output: { + type: "object", + objectApiName: "Employee", + objectTypeApiName: "Employee", + }, + rid: + "ri.function-registry.main.function.9b55870a-63c7-4d48-8f06-9627c0805968", + version: "0.11.0", +}; + +export const queryTypeAcceptsObjectSets: QueryTypeV2 = { + apiName: "queryAcceptsObjectSets", + description: "description of the query that takes objectSet types", + displayName: "QueryAcceptsObjectSets", + parameters: { + objectSet: { + dataType: { + type: "objectSet", + objectApiName: "Employee", + objectTypeApiName: "Employee", + }, + }, + }, + output: { + type: "objectSet", + objectApiName: "Employee", + objectTypeApiName: "Employee", + }, + rid: + "ri.function-registry.main.function.9b55870a-63c7-4d48-8f06-9627c0805968", + version: "0.11.0", +}; export const queryTypes: QueryTypeV2[] = [ addOneQueryType, queryTypeReturnsStruct, @@ -198,4 +319,8 @@ export const queryTypes: QueryTypeV2[] = [ queryTypeReturnsTimestamp, queryTypeTwoDimensionalAggregation, queryTypeThreeDimensionalAggregation, + queryTypeAcceptsObjects, + queryTypeAcceptsObjectSets, + queryTypeAcceptsTwoDimensionalAggregation, + queryTypeAcceptsThreeDimensionalAggregation, ];