diff --git a/examples/basic/cli/src/examples/fetchAggregationForEmployees.ts b/examples/basic/cli/src/examples/fetchAggregationForEmployees.ts index cf0867271..10c5c5c3e 100644 --- a/examples/basic/cli/src/examples/fetchAggregationForEmployees.ts +++ b/examples/basic/cli/src/examples/fetchAggregationForEmployees.ts @@ -15,7 +15,7 @@ */ import type { Client } from "@osdk/client"; -import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; import invariant from "tiny-invariant"; import type { TypeOf } from "ts-expect"; import { expectType } from "ts-expect"; @@ -23,7 +23,7 @@ import { expectType } from "ts-expect"; export async function fetchAggregationForEmployees( client: Client, ) { - const result = await client.objectSet("Employee").aggregateOrThrow({ + const result = await client(Employee).aggregateOrThrow({ select: { locationCity: "approximateDistinct", locationName: "approximateDistinct", diff --git a/examples/basic/cli/src/examples/fetchAggregationForEmployeesGrouped.ts b/examples/basic/cli/src/examples/fetchAggregationForEmployeesGrouped.ts index bc3dd0c20..84c25a4c0 100644 --- a/examples/basic/cli/src/examples/fetchAggregationForEmployeesGrouped.ts +++ b/examples/basic/cli/src/examples/fetchAggregationForEmployeesGrouped.ts @@ -15,7 +15,7 @@ */ import type { Client } from "@osdk/client"; -import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; import invariant from "tiny-invariant"; import type { TypeOf } from "ts-expect"; import { expectType } from "ts-expect"; @@ -23,7 +23,7 @@ import { expectType } from "ts-expect"; export async function fetchAggregationForEmployeesGrouped( client: Client, ) { - const result = await client.objectSet("Employee").aggregateOrThrow({ + const result = await client(Employee).aggregateOrThrow({ select: { locationCity: "approximateDistinct", locationName: "approximateDistinct", diff --git a/examples/basic/cli/src/examples/fetchAggregationForEmployeesGroupedThin.ts b/examples/basic/cli/src/examples/fetchAggregationForEmployeesGroupedThin.ts index 826d8d11c..4af01e893 100644 --- a/examples/basic/cli/src/examples/fetchAggregationForEmployeesGroupedThin.ts +++ b/examples/basic/cli/src/examples/fetchAggregationForEmployeesGroupedThin.ts @@ -16,7 +16,7 @@ import type { ClientContext } from "@osdk/client"; import { aggregateOrThrow } from "@osdk/client/objects"; -import { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; import invariant from "tiny-invariant"; import type { TypeOf } from "ts-expect"; import { expectType } from "ts-expect"; @@ -26,7 +26,7 @@ export async function fetchAggregationForEmployeesGroupedThin( ) { const result = await aggregateOrThrow( clientCtx, - Ontology.objects.Employee, + Employee, { type: "base", objectType: "Employee", diff --git a/examples/basic/cli/src/examples/fetchEmployeeLead.ts b/examples/basic/cli/src/examples/fetchEmployeeLead.ts index e816b46ca..2ad1bc33d 100644 --- a/examples/basic/cli/src/examples/fetchEmployeeLead.ts +++ b/examples/basic/cli/src/examples/fetchEmployeeLead.ts @@ -15,7 +15,7 @@ */ import type { Client } from "@osdk/client"; -import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; import type { TypeOf } from "ts-expect"; import { expectType } from "ts-expect"; @@ -23,7 +23,7 @@ export async function fetchEmployeeLead( client: Client, adUsername: string, ) { - const result = await client.objects.Employee.where({ + const result = await client(Employee).where({ adUsername, }) .pivotTo("lead") diff --git a/examples/basic/cli/src/examples/fetchEmployeePage.ts b/examples/basic/cli/src/examples/fetchEmployeePage.ts index a9cf1e2e3..1b9fc1f09 100644 --- a/examples/basic/cli/src/examples/fetchEmployeePage.ts +++ b/examples/basic/cli/src/examples/fetchEmployeePage.ts @@ -15,11 +15,12 @@ */ import type { Client, Osdk } from "@osdk/client"; -import type { Employee, Ontology } from "@osdk/examples.basic.sdk"; +import { Employee } from "@osdk/examples.basic.sdk"; +import type { Ontology } from "@osdk/examples.basic.sdk"; import { expectType } from "ts-expect"; export async function fetchEmployeePage(client: Client) { - const result = await client.objectSet("Employee").fetchPageOrThrow(); + const result = await client(Employee).fetchPageOrThrow(); expectType(""); // FIXME: this isn't strict enough of a check for below expectType(result.data[0].businessTitle); diff --git a/examples/basic/cli/src/examples/fetchEmployeePageByAdUsername.ts b/examples/basic/cli/src/examples/fetchEmployeePageByAdUsername.ts index 161698344..ffca4e968 100644 --- a/examples/basic/cli/src/examples/fetchEmployeePageByAdUsername.ts +++ b/examples/basic/cli/src/examples/fetchEmployeePageByAdUsername.ts @@ -15,7 +15,7 @@ */ import type { Client } from "@osdk/client"; -import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; /** * Demonstrates looking up an employee and for fun adds an `AND` and `ne` @@ -24,7 +24,7 @@ export async function fetchEmployeePageByAdUsername( client: Client, adUsername: string, ) { - const result = await client.objects.Employee.where({ + const result = await client(Employee).where({ $and: [{ adUsername }, { employeeNumber: { $ne: 5 } }], }).fetchPageOrThrow(); // for await (const e of client.objects.Employee.asyncIter()) { diff --git a/examples/basic/cli/src/examples/fetchEmployeePageByAdUsernameAndLimit.ts b/examples/basic/cli/src/examples/fetchEmployeePageByAdUsernameAndLimit.ts index 8c29c1e6f..4c3091832 100644 --- a/examples/basic/cli/src/examples/fetchEmployeePageByAdUsernameAndLimit.ts +++ b/examples/basic/cli/src/examples/fetchEmployeePageByAdUsernameAndLimit.ts @@ -15,7 +15,7 @@ */ import type { Client } from "@osdk/client"; -import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee, type Ontology } from "@osdk/examples.basic.sdk"; import invariant from "tiny-invariant"; /** @@ -25,7 +25,7 @@ export async function fetchEmployeePageByAdUsernameAndLimit( client: Client, adUsername: string, ) { - const result = await client.objects.Employee.where({ + const result = await client(Employee).where({ $and: [ { adUsername }, { employeeNumber: { $ne: 5 } }, diff --git a/examples/basic/cli/src/examples/fetchEmployeePageThin.ts b/examples/basic/cli/src/examples/fetchEmployeePageThin.ts index 3c6214533..2f8522af9 100644 --- a/examples/basic/cli/src/examples/fetchEmployeePageThin.ts +++ b/examples/basic/cli/src/examples/fetchEmployeePageThin.ts @@ -18,14 +18,15 @@ import * as OsdkApi from "@osdk/client"; import type { ClientContext } from "@osdk/client"; import { Objects } from "@osdk/client"; import { fetchPageOrThrow } from "@osdk/client/objects"; -import { Ontology } from "@osdk/examples.basic.sdk"; +import type { Ontology } from "@osdk/examples.basic.sdk"; +import { Employee } from "@osdk/examples.basic.sdk"; import type { TypeOf } from "ts-expect"; import { expectType } from "ts-expect"; export async function fetchEmployeePageThin( clientCtx: ClientContext, ) { - let result = await fetchPageOrThrow(clientCtx, Ontology.objects.Employee, { + let result = await fetchPageOrThrow(clientCtx, Employee, { select: ["adUsername", "businessTitle", "employeeNumber"], }); @@ -42,29 +43,17 @@ export async function fetchEmployeePageThin( >(false); // OR - let result2 = await Objects.fetchPageOrThrow( - clientCtx, - Ontology.objects.Employee, - { - select: ["adUsername", "businessTitle", "employeeNumber"], - }, - ); + let result2 = await Objects.fetchPageOrThrow(clientCtx, Employee, { + select: ["adUsername", "businessTitle", "employeeNumber"], + }); // or - let result3 = await OsdkApi.Objects.fetchPageOrThrow( - clientCtx, - Ontology.objects.Employee, - { - select: ["adUsername", "businessTitle", "employeeNumber"], - }, - ); + let result3 = await OsdkApi.Objects.fetchPageOrThrow(clientCtx, Employee, { + select: ["adUsername", "businessTitle", "employeeNumber"], + }); // Quick check to make sure we get everything - let result4 = await fetchPageOrThrow( - clientCtx, - Ontology.objects.Employee, - {}, - ); + let result4 = await fetchPageOrThrow(clientCtx, Employee, {}); console.log("fetchEmployeePageThin(): "); console.table( diff --git a/examples/basic/cli/src/index.ts b/examples/basic/cli/src/index.ts index 0071bdb33..eb0df2380 100644 --- a/examples/basic/cli/src/index.ts +++ b/examples/basic/cli/src/index.ts @@ -15,7 +15,11 @@ */ import { createClient, createClientContext } from "@osdk/client"; -import { Ontology } from "@osdk/examples.basic.sdk"; +import { + BoundariesUsState, + Ontology, + WeatherStation, +} from "@osdk/examples.basic.sdk"; import invariant from "tiny-invariant"; import { fetchAggregationForEmployees } from "./examples/fetchAggregationForEmployees.js"; import { fetchAggregationForEmployeesGrouped } from "./examples/fetchAggregationForEmployeesGrouped.js"; @@ -54,27 +58,31 @@ export const clientCtx = createClientContext( `typescript-sdk/dev osdk-cli/dev`, ); +const runOld = false; + async function runTests() { try { - await fetchEmployeePage(client); - await fetchEmployeePageByAdUsername(client, "fish"); - await fetchEmployeePageByAdUsernameAndLimit(client, "fish"); - await fetchAggregationForEmployees(client); - await fetchAggregationForEmployeesGrouped(client); - await fetchEmployeePageThin(clientCtx); - - await fetchAggregationForEmployeesGroupedThin(clientCtx); - await fetchEmployeeLead(client, "bob"); - - const interfaceImplementationComplete = false; - if (interfaceImplementationComplete) { - const interfaceResults = await client.objects.SimpleInterface - .fetchPageOrThrow(); - interfaceResults.data[0].body; + if (runOld) { + await fetchEmployeePage(client); + await fetchEmployeePageByAdUsername(client, "fish"); + await fetchEmployeePageByAdUsernameAndLimit(client, "fish"); + await fetchAggregationForEmployees(client); + await fetchAggregationForEmployeesGrouped(client); + await fetchEmployeePageThin(clientCtx); + + await fetchAggregationForEmployeesGroupedThin(clientCtx); + await fetchEmployeeLead(client, "bob"); + + const interfaceImplementationComplete = false; + if (interfaceImplementationComplete) { + const interfaceResults = await client.objects.SimpleInterface + .fetchPageOrThrow(); + interfaceResults.data[0].body; + } } // only works in default ontology - const result = await client.objects.WeatherStation.where({ + const result = await client(WeatherStation).where({ geohash: { $within: { distance: [1_000, "miles"], @@ -86,7 +94,7 @@ async function runTests() { console.log(result.data[0].geohash); // drew a polygon that intersects NY, NJ and PA - const intersectResult = await client.objects.BoundariesUsState.where({ + const intersectResult = await client(BoundariesUsState).where({ geometry10M: { $intersects: { polygon: [ @@ -120,7 +128,7 @@ async function runTests() { console.log(intersectResult.data.map(data => data.usState)); console.log(intersectResult.data[0].geometry10M); - const intersectResultGeojson = await client.objects.BoundariesUsState + const intersectResultGeojson = await client(BoundariesUsState) .where({ $not: { geometry10M: { @@ -159,7 +167,7 @@ async function runTests() { console.log(intersectResultGeojson.data.map(data => data.usState)); // drew a bbox that intersects NY, NJ and PA - const intersectResultbbox = await client.objects.BoundariesUsState + const intersectResultbbox = await client(BoundariesUsState) .where({ geometry10M: { $intersects: [ @@ -173,7 +181,7 @@ async function runTests() { console.log(intersectResultbbox.data.map(data => data.usState)); - await typeChecks(client); + if (runOld) await typeChecks(client); } catch (e) { console.error("Caught an error we did not expect", typeof e); console.error(e); diff --git a/examples/todoapp/src/useTodos.tsx b/examples/todoapp/src/useTodos.tsx index 9c70f73cc..5e9c8ddaf 100644 --- a/examples/todoapp/src/useTodos.tsx +++ b/examples/todoapp/src/useTodos.tsx @@ -4,6 +4,7 @@ import { foundryClient, foundryClient2 } from "./foundryClient"; import { isOk, ReturnEditsMode, type Result } from "./generatedNoCheck"; import type { Todo } from "./generatedNoCheck/ontology/objects"; import { ActionValidationError } from "@osdk/client"; +import * as MyOsdk from "./generatedNoCheck2"; function orThrow(result: Result) { if (isOk(result)) { @@ -21,7 +22,7 @@ export function useTodos() { ); useEffect(() => { - const unsubscribe = foundryClient2.objects.Todo.subscribe({ + const unsubscribe = foundryClient2(MyOsdk.Todo).subscribe({ onChange(objects) { // index incoming objects by apiName and then by pk value const byApiNameByPK = new Map< @@ -97,7 +98,7 @@ export function useTodos() { const actionsV2Ready = true; if (actionsV2Ready) { - await foundryClient2.actions.completeTodo({ + await foundryClient2(MyOsdk.completeTodo)({ is_complete: b, Todo: todo.__primaryKey, }); @@ -126,14 +127,14 @@ export function useTodos() { [mutate] ); - const createTodo = useCallback( + const createTodoMutator = useCallback( async (title: string, setError?: (error: string | undefined) => void) => { await mutate( async () => { // Unwrap to get throw behavior on error. // Don't return because we want to invalidate cache try { - await foundryClient2.actions.createTodo({ + await foundryClient2(MyOsdk.createTodo)({ Todo: title, is_complete: false, }); @@ -181,7 +182,7 @@ export function useTodos() { error, isValidating, toggleComplete, - createTodo, + createTodo: createTodoMutator, }; } diff --git a/packages/api/changelog/@unreleased/pr-83.v2.yml b/packages/api/changelog/@unreleased/pr-83.v2.yml new file mode 100644 index 000000000..a0be79c70 --- /dev/null +++ b/packages/api/changelog/@unreleased/pr-83.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Add new client invocation syntax + links: + - https://github.com/palantir/osdk-ts/pull/83 diff --git a/packages/client/changelog/@unreleased/pr-83.v2.yml b/packages/client/changelog/@unreleased/pr-83.v2.yml new file mode 100644 index 000000000..a0be79c70 --- /dev/null +++ b/packages/client/changelog/@unreleased/pr-83.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Add new client invocation syntax + links: + - https://github.com/palantir/osdk-ts/pull/83 diff --git a/packages/client/src/Client.ts b/packages/client/src/Client.ts index f14d3ab51..6d5980b44 100644 --- a/packages/client/src/Client.ts +++ b/packages/client/src/Client.ts @@ -15,22 +15,33 @@ */ import type { + ActionDefinition, + ObjectOrInterfaceDefinition, ObjectOrInterfaceDefinitionFrom, ObjectOrInterfaceKeysFrom, ObjectTypeKeysFrom, OntologyDefinition, } from "@osdk/api"; -import type { Actions } from "./actions/Actions.js"; +import type { Actions, ActionSignatureFromDef } from "./actions/Actions.js"; import type { ObjectSet } from "./objectSet/ObjectSet.js"; import type { ObjectSetCreator } from "./ObjectSetCreator.js"; export interface Client> { + < + Q extends ObjectOrInterfaceDefinition | ActionDefinition, + >(o: Q): Q extends ObjectOrInterfaceDefinition ? ObjectSet + : Q extends ActionDefinition ? ActionSignatureFromDef + : never; + + /** @deprecated use client(MyType) */ objectSet: >( type: K, ) => ObjectSet>; + /** @deprecated use client(MyType) */ objects: ObjectSetCreator; + /** @deprecated use client(myAction) */ actions: Actions; __UNSTABLE_preexistingObjectSet>( diff --git a/packages/client/src/actions/Actions.ts b/packages/client/src/actions/Actions.ts index 5b9b8f547..6e74383e7 100644 --- a/packages/client/src/actions/Actions.ts +++ b/packages/client/src/actions/Actions.ts @@ -15,6 +15,7 @@ */ import type { + ActionDefinition, ActionParameterDefinition, ObjectActionDataType, ObjectSetActionDataType, @@ -68,11 +69,13 @@ export type OsdkActionParameters< > = NullableProps extends never ? NotOptionalParams : PartialByNotStrict, NullableProps>; +export type ActionSignatureFromDef> = + NonNullable extends never + ? ActionSignature + : NonNullable; + export type Actions> = { - [K in keyof O["actions"]]: - NonNullable extends never - ? ActionSignature - : NonNullable; + [K in keyof O["actions"]]: ActionSignatureFromDef; }; type ActionParametersDefinition = Record< diff --git a/packages/client/src/createClient.ts b/packages/client/src/createClient.ts index f981b2fd7..375584590 100644 --- a/packages/client/src/createClient.ts +++ b/packages/client/src/createClient.ts @@ -15,16 +15,19 @@ */ import type { + ActionDefinition, + ObjectOrInterfaceDefinition, ObjectOrInterfaceDefinitionFrom, ObjectOrInterfaceKeysFrom, ObjectTypeKeysFrom, OntologyDefinition, } from "@osdk/api"; import { createClientContext } from "@osdk/shared.net"; +import type { ActionSignatureFromDef } from "./actions/Actions.js"; import { createActionInvoker } from "./actions/createActionInvoker.js"; import type { Client } from "./Client.js"; import { createObjectSet } from "./objectSet/createObjectSet.js"; -import type { ObjectSetFactory } from "./objectSet/ObjectSet.js"; +import type { ObjectSet, ObjectSetFactory } from "./objectSet/ObjectSet.js"; import { createObjectSetCreator } from "./ObjectSetCreator.js"; import { USER_AGENT } from "./util/UserAgent.js"; @@ -54,13 +57,30 @@ export function createClient>( clientCtx, ); + const actionInvoker = createActionInvoker(clientCtx); + + function newClient< + T extends ObjectOrInterfaceDefinition | ActionDefinition, + >(o: T): T extends ObjectOrInterfaceDefinition ? ObjectSet + : T extends ActionDefinition ? ActionSignatureFromDef + : never + { + if (o.type === "object" || o.type === "interface") { + return createObjectSet(o, clientCtx) as ObjectSet as any; + } else if (o.type === "action") { + return actionInvoker[o.apiName]; + } else { + throw new Error("Unknown definition: " + JSON.stringify(o)); + } + } + const client: Client = Object.defineProperties( - {} as Client, + newClient as Client, { objectSet: { get: () => objectSetFactory }, objects: { get: () => createObjectSetCreator(client, clientCtx) }, actions: { - get: () => createActionInvoker(clientCtx), + get: () => actionInvoker, }, __UNSTABLE_preexistingObjectSet: { get: () => diff --git a/packages/generator/changelog/@unreleased/pr-83.v2.yml b/packages/generator/changelog/@unreleased/pr-83.v2.yml new file mode 100644 index 000000000..a0be79c70 --- /dev/null +++ b/packages/generator/changelog/@unreleased/pr-83.v2.yml @@ -0,0 +1,5 @@ +type: feature +feature: + description: Add new client invocation syntax + links: + - https://github.com/palantir/osdk-ts/pull/83