Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds an example for subscriptions #990

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions etc/client.report.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { InterfaceMetadata } from '@osdk/api';
import { isOk } from '@osdk/api';
import type { MinimalObjectSet } from '@osdk/api/unstable';
import { ObjectMetadata } from '@osdk/api';
import type { ObjectOrInterfaceDefinition } from '@osdk/api';
import type { ObjectQueryDataType } from '@osdk/api';
import { ObjectSet } from '@osdk/api';
import type { ObjectSetQueryDataType } from '@osdk/api';
Expand Down Expand Up @@ -94,6 +95,9 @@ export interface Client extends SharedClient, SharedClient_2 {
fetchMetadata<Q extends (ObjectTypeDefinition | InterfaceDefinition | ActionDefinition<any> | QueryDefinition<any>)>(o: Q): Promise<Q extends ObjectTypeDefinition ? ObjectMetadata : Q extends InterfaceDefinition ? InterfaceMetadata : Q extends ActionDefinition<any> ? ActionMetadata : Q extends QueryDefinition<any> ? QueryMetadata : never>;
}

// @public (undocumented)
export function consolidateOsdkObject<T extends Osdk.Instance<V, any, any>, U extends Osdk.Instance<V, any, any>, V extends ObjectOrInterfaceDefinition>(oldObject: T | undefined, upToDateObject: U): U;

// @public (undocumented)
export function createAttachmentUpload(data: Blob, name: string): AttachmentUpload;

Expand Down
51 changes: 50 additions & 1 deletion examples-extra/docs_example/src/osdkExample.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { createClient } from "@osdk/client";
import { consolidateOsdkObject, createClient, Osdk } from "@osdk/client";
import {
$ontologyRid,
Employee,
Expand Down Expand Up @@ -182,4 +182,53 @@ export async function osdkObjectSetExample() {
unionObjectSet,
subtractObjectSet,
);

/**
* SUBSCRIPTIONS
*/

const objects: Record<string, Osdk.Instance<Employee, never, "employeeId">> =
{};

const subscription = client(Employee).subscribe({
onChange: (update) => {
if (update.state === "ADDED_OR_UPDATED") {
console.log(
`Employee with primary key ${update.object.$primaryKey} was added or updated`,
);

objects[update.object.$primaryKey] = consolidateOsdkObject(
objects[update.object.$primaryKey],
update.object,
);
} else if (update.state === "REMOVED") {
console.log(
`Employee with primary key ${update.object.$primaryKey} was deleted`,
);

delete objects[update.object.$primaryKey];
}
},
onOutOfDate: async () => {
console.log("Object Set Out of Date. Reloading Object Set");

for await (const obj of client(Employee).asyncIter()) {
objects[obj.$primaryKey] = obj;
}
},
onSuccessfulSubscription() {
console.log("Subscription successful");
setTimeout(() => {
subscription.unsubscribe();
}, 10000);
},
onError(err) {
console.error("Error in subscription and subscription closed", err);
},
// The properties that will be returned in updates can optionally be specified below. Updates may still be sent for properties not specified here,
// and not all properties may be returned.
}, { properties: ["employeeId"] });

// An empty subscription will request all properties
client(Employee).subscribe({ onChange: () => {} });
}
2 changes: 2 additions & 0 deletions packages/client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,5 @@ export {
extractDateInLocalTime,
extractDateInUTC,
} from "./util/datetimeConverters.js";

export { consolidateOsdkObject } from "./util/consolidateOsdkObject.js";
39 changes: 39 additions & 0 deletions packages/client/src/util/consolidateOsdkObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*
* 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 { ObjectOrInterfaceDefinition, Osdk } from "@osdk/api";

export function consolidateOsdkObject<
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not used anymore, will delete

T extends Osdk.Instance<V, any, any>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you capturing T and U separately if they're the same type?

Copy link
Contributor Author

@nihalbhatnagar nihalbhatnagar Nov 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I capture them separately since I want to specify the return type to be one or the other. I want the ability for someon to pass in Osdk.Instance<A> and Osdk.Instance<A, never "propA"> and return the type in the original. I tested this locally and this is the case that the type is narrowed properly. Note: I need to swap the return type to be T instead of U

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The bigger question I have here is which one should we return. I think we should always scope the object down to the oldObject. So if oldObject has a more narrow definition, we want the oldDefinition to be returned and also delete extraneous keys off of the new object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The edge case that's left out here is if someone wants an object scoped down to Object B. They can't just reverse what they put in since we explicitly use the properties on the latter object.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this exclusion is ok. In my mind, this can be used for loadObject and subscriptions, where they already have a defined store. For either, they will likely want an object in the shape of that store instead of what they receive necessarily on the load or subscribe. I can't really imagine why they would want the edge case described above

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If so, maybe we rename as such then. I was confused because earlier it seemed like you were just expanding to whatever the new return type was

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We just need this name to be clear that you're updating existing properties, without expanding the property scope of the old object (even if the new object has more props)

U extends Osdk.Instance<V, any, any>,
V extends ObjectOrInterfaceDefinition,
>(
oldObject: T | undefined,
upToDateObject: U,
): U {
if (!oldObject) {
return upToDateObject;
}
const combinedObject = oldObject as any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this any cast? If we don't capture them separately you can probably use the object as is

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So when I update the return type, I'm going to remove this any cast and just iterate on object B based on the keys of object A and update the old object.


for (const key in upToDateObject) {
if (upToDateObject.hasOwnProperty(key)) {
combinedObject[key] = upToDateObject[key];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, also I think we may need to update the internal values here right? Not totally sure so double check me, but if someone starts like casting things $as and back, the underlying data may not actually reflect?

}
}

return combinedObject;
}
56 changes: 56 additions & 0 deletions packages/client/src/util/util.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* 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 { Osdk } from "@osdk/api";
import type { Employee, Todo } from "@osdk/client.test.ontology";
import { describe, expect, expectTypeOf, it } from "vitest";
import { consolidateOsdkObject } from "./consolidateOsdkObject.js";

describe(consolidateOsdkObject, () => {
it("combines two objects", () => {
const oldObject: Osdk.Instance<Todo, never, "text"> = {
$apiName: "Todo",
$objectType: "type",
$primaryKey: 1,
$title: "Employee",
text: "text",
} as any;

const upToDateObject: Osdk.Instance<Todo> = {
$apiName: "Todo",
$objectType: "type",
$primaryKey: 1,
$title: "Employee",
id: 1,
text: "hi",
} as any;

const result = consolidateOsdkObject(oldObject, upToDateObject);

expectTypeOf(result).toEqualTypeOf<Osdk.Instance<Todo>>();

expect(result).toMatchInlineSnapshot(`
{
"$apiName": "Todo",
"$objectType": "type",
"$primaryKey": 1,
"$title": "Employee",
"id": 1,
"text": "hi",
}
`);
});
});
Loading