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

Interfaces for Subscribe #1018

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
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
5 changes: 5 additions & 0 deletions .changeset/forty-ligers-scream.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@osdk/client": patch
---

Allows interfaces to be used with subscribe
70 changes: 49 additions & 21 deletions packages/client/src/objectSet/ObjectSetListenerWebsocket.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,10 @@ import WebSocket from "isomorphic-ws";
import invariant from "tiny-invariant";
import type { Logger } from "../Logger.js";
import type { ClientCacheKey, MinimalClient } from "../MinimalClientContext.js";
import { convertWireToOsdkObjects } from "../object/convertWireToOsdkObjects.js";
import {
convertWireToOsdkObjects,
convertWireToOsdkObjects2,
} from "../object/convertWireToOsdkObjects.js";

const MINIMUM_RECONNECT_DELAY_MS = 5 * 1000;

Expand Down Expand Up @@ -65,7 +68,8 @@ interface Subscription<
> {
listener: Required<ObjectSetListener<Q, P>>;
objectSet: ObjectSet;
primaryKeyPropertyName: string;
interfaceApiName?: string;
primaryKeyPropertyName?: string;
requestedProperties: Array<P>;
requestedReferenceProperties: Array<P>;
subscriptionId: string;
Expand Down Expand Up @@ -173,31 +177,49 @@ export class ObjectSetListenerWebsocket {
globalThis.crypto ??= (await import("node:crypto")).webcrypto as any;
}

const objDef = await this.#client.ontologyProvider.getObjectDefinition(
objectType.apiName,
);
const objDef = objectType.type === "object"
? await this.#client.ontologyProvider.getObjectDefinition(
objectType.apiName,
)
: await this.#client.ontologyProvider.getInterfaceDefinition(
objectType.apiName,
);

if (properties.length === 0) {
properties = Object.keys(objDef.properties) as Array<P>;
}
let objectProperties: Array<P> = [];
let referenceProperties: Array<P> = [];

const objectProperties = properties.filter((p) =>
objDef.properties[p].type !== "geotimeSeriesReference"
);
const referenceProperties = properties.filter((p) =>
objDef.properties[p].type === "geotimeSeriesReference"
);
if (objectType.type === "object") {
if (properties.length === 0) {
properties = Object.keys(objDef.properties) as Array<P>;
}

objectProperties = properties.filter((p) =>
objDef.properties[p].type !== "geotimeSeriesReference"
);

referenceProperties = properties.filter((p) =>
objDef.properties[p].type === "geotimeSeriesReference"
);
} else {
objectProperties = [];
Copy link
Contributor Author

Choose a reason for hiding this comment

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

OSW assumes the names we provide them are the API names for the actual object type implementations instead of the SPT names. Therefore the current API I decided on was assuming if you pass an interface in that you are only specifying reference properties. This does require any casting what is passed in, and I think it's a larger conversation if the TS OSDK supports specifying anything beyond an SPT.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There are ongoing discussions to allow OSW to take in the SPT names instead of the object type property API names here. I'll also open up the discussion as to how we want to allow requesting reference properties in the finalized API

referenceProperties = properties;
}

const sub: Subscription<Q, P> = {
listener: fillOutListener<Q, P>(listener),
objectSet,
primaryKeyPropertyName: objDef.primaryKeyApiName,
primaryKeyPropertyName: objDef.type === "interface"
? undefined
: objDef.primaryKeyApiName,
requestedProperties: objectProperties,
requestedReferenceProperties: referenceProperties,
status: "preparing",
// Since we don't have a real subscription id yet but we need to keep
// track of this reference, we can just use a random uuid.
subscriptionId: `TMP-${crypto.randomUUID()}`,
interfaceApiName: objDef.type === "object"
? undefined
: objDef.apiName,
};

this.#subscriptions.set(sub.subscriptionId, sub);
Expand Down Expand Up @@ -273,7 +295,12 @@ export class ObjectSetListenerWebsocket {
const subscribe: ObjectSetStreamSubscribeRequests = {
id,
requests: readySubs.map<ObjectSetStreamSubscribeRequest>((
{ objectSet, requestedProperties, requestedReferenceProperties },
{
objectSet,
requestedProperties,
requestedReferenceProperties,
interfaceApiName,
},
) => {
return {
objectSet: objectSet,
Expand Down Expand Up @@ -432,15 +459,17 @@ export class ObjectSetListenerWebsocket {
);
const osdkObjectsWithReferenceUpdates = await Promise.all(
referenceUpdates.map(async (o) => {
const osdkObjectArray = await convertWireToOsdkObjects(
const osdkObjectArray = await this.#client.objectFactory(
Copy link
Contributor Author

@nihalbhatnagar nihalbhatnagar Nov 25, 2024

Choose a reason for hiding this comment

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

I'm using the old interface API's since the our new implementations require an interface to object type properties map. I could manually create this, but I think it would be best to talk through this @ssanjay1 and just unblock for right now

this.#client,
[{
__apiName: o.objectType,
__primaryKey: o.primaryKey[sub.primaryKeyPropertyName],
__primaryKey: sub.primaryKeyPropertyName != null
? o.primaryKey[sub.primaryKeyPropertyName]
: undefined,
...o.primaryKey,
[o.property]: o.value,
}],
undefined,
sub.interfaceApiName,
) as Array<Osdk.Instance<any, never, any>>;
const singleOsdkObject = osdkObjectArray[0] ?? undefined;
return singleOsdkObject != null
Expand All @@ -465,11 +494,10 @@ export class ObjectSetListenerWebsocket {
for (const key of keysToDelete) {
delete o.object[key];
}

const osdkObjectArray = await this.#client.objectFactory(
this.#client,
[o.object],
undefined,
sub.interfaceApiName,
) as Array<Osdk.Instance<any, never, any>>;
const singleOsdkObject = osdkObjectArray[0] ?? undefined;
return singleOsdkObject != null
Expand Down
23 changes: 22 additions & 1 deletion packages/e2e.generated.catchall/ontology.json
Original file line number Diff line number Diff line change
Expand Up @@ -245,13 +245,18 @@
"visibility": "NORMAL"
},
"linkTypes": [],
"implementsInterfaces": ["FooInterface"],
"implementsInterfaces": ["FooInterface", "OsdkTestInterface"],
"implementsInterfaces2": {
"FooInterface": {
"properties": {
"name": "osdkObjectName",
"description": "description"
}
},
"OsdkTestInterface": {
"properties": {
"objectDescription": "description"
}
}
},
"sharedPropertyTypeMapping": {}
Expand Down Expand Up @@ -1294,6 +1299,22 @@
},
"rid": "ri.ontology.main.interface-type.1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b",
"status": "ACTIVE"
},
"OsdkTestInterface": {
"apiName": "OsdkTestInterface",
"displayName": "OsdkTestInterface",
"description": "OsdkTestInterface",
"properties": {
"objectDescription": {
"rid": "ri.ontology.main.shared-property.751ed7ee-5d2c-41a1-bf60-9cbef5623f23",
"apiName": "objectDescription",
"dataType": {
"type": "string"
}
}
},
"rid": "ri.ontology.main.interface.06c534fd-4f68-44d9-b268-72729a47eaab",
"status": "ACTIVE"
}
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export {
editOsdkTestObject,
} from './ontology/actions.js';
export * as $Actions from './ontology/actions.js';
export { FooInterface, InterfaceNoProps } from './ontology/interfaces.js';
export { FooInterface, InterfaceNoProps, OsdkTestInterface } from './ontology/interfaces.js';
export * as $Interfaces from './ontology/interfaces.js';
export {
BoundariesUsState,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export { FooInterface } from './interfaces/FooInterface.js';
export { InterfaceNoProps } from './interfaces/InterfaceNoProps.js';
export { OsdkTestInterface } from './interfaces/OsdkTestInterface.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
import type { PropertyDef as $PropertyDef } from '@osdk/client';
import { $osdkMetadata } from '../../OntologyMetadata.js';

import type {
InterfaceDefinition as $InterfaceDefinition,
ObjectSet as $ObjectSet,
Osdk as $Osdk,
PropertyValueWireToClient as $PropType,
} from '@osdk/client';

export type OsdkObjectLinks$OsdkTestInterface = {};

export namespace OsdkTestInterface {
export type PropertyKeys = 'objectDescription';

export interface Props {
readonly objectDescription: $PropType['string'] | undefined;
}
export type StrictProps = Props;

export interface ObjectSet extends $ObjectSet<OsdkTestInterface, OsdkTestInterface.ObjectSet> {}

export type OsdkInstance<
OPTIONS extends never | '$rid' = never,
K extends keyof OsdkTestInterface.Props = keyof OsdkTestInterface.Props,
> = $Osdk.Instance<OsdkTestInterface, OPTIONS, K>;

/** @deprecated use OsdkInstance */
export type OsdkObject<
OPTIONS extends never | '$rid' = never,
K extends keyof OsdkTestInterface.Props = keyof OsdkTestInterface.Props,
> = OsdkInstance<OPTIONS, K>;
}

export interface OsdkTestInterface extends $InterfaceDefinition {
osdkMetadata: typeof $osdkMetadata;
type: 'interface';
apiName: 'OsdkTestInterface';
__DefinitionMetadata?: {
objectSet: OsdkTestInterface.ObjectSet;
props: OsdkTestInterface.Props;
linksType: OsdkObjectLinks$OsdkTestInterface;
strictProps: OsdkTestInterface.StrictProps;
apiName: 'OsdkTestInterface';
description: 'OsdkTestInterface';
displayName: 'OsdkTestInterface';
links: {};
properties: {
/**
* (no ontology metadata)
*/
objectDescription: $PropertyDef<'string', 'nullable', 'single'>;
};
rid: 'ri.ontology.main.interface.06c534fd-4f68-44d9-b268-72729a47eaab';
type: 'interface';
};
}

export const OsdkTestInterface: OsdkTestInterface = {
type: 'interface',
apiName: 'OsdkTestInterface',
osdkMetadata: $osdkMetadata,
};
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,24 @@ export interface OsdkTestObject extends $ObjectTypeDefinition {
color: '#4C90F0';
name: 'cube';
};
implements: ['FooInterface'];
implements: ['FooInterface', 'OsdkTestInterface'];
interfaceMap: {
FooInterface: {
name: 'osdkObjectName';
description: 'description';
};
OsdkTestInterface: {
objectDescription: 'description';
};
};
inverseInterfaceMap: {
FooInterface: {
osdkObjectName: 'name';
description: 'description';
};
OsdkTestInterface: {
description: 'objectDescription';
};
};
links: {};
pluralDisplayName: 'Osdk Test Objects';
Expand Down
29 changes: 28 additions & 1 deletion packages/e2e.sandbox.catchall/src/runSubscriptionsTest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,12 @@ import {
__EXPERIMENTAL__NOT_SUPPORTED_YET__preexistingObjectSet,
__EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe,
} from "@osdk/api/unstable";
import { $Actions, MtaBus, OsdkTestObject } from "@osdk/e2e.generated.catchall";
import {
$Actions,
MtaBus,
OsdkTestInterface,
OsdkTestObject,
} from "@osdk/e2e.generated.catchall";
import { client, dsClient } from "./client.js";

export async function runSubscriptionsTest() {
Expand Down Expand Up @@ -67,6 +72,28 @@ export async function runSubscriptionsTest() {
{ properties: ["stringProperty"] },
);

const interfaceSubscription = client(OsdkTestInterface).subscribe({
onChange(object) {
console.log(
"Interface with primaryKey ",
object.object.$primaryKey,
" changed objectDescription to ",
object.object.objectDescription,
);
},
async onSuccessfulSubscription() {
console.log("Successfully subscribed to OsdkTestInterface");
await client($Actions.createOsdkTestObject).applyAction({
description: "test",
osdk_object_name: "OsdkTestObject",
string_property: "test",
});
},
onError(err) {
console.error("Error in interface subscription: ", err);
},
});

const mtaBusSubscription = dsClient(
__EXPERIMENTAL__NOT_SUPPORTED_YET_subscribe,
).subscribe(
Expand Down